Skip to content

Safe wrappers for PEP 587 initialization #1474

@davidhewitt

Description

@davidhewitt

This is a follow-on from the discussion in indygreg/PyOxidizer#324.

cc @indygreg @wkschwartz

The issue mentioned there is that the ffi bindings in initconfig.rs for PyPreConfig and PyConfig are cumbersome to initialize safely, so it would be helpful for us to provide safe wrappers for PyPreConfig_InitPythonConfig etc.

Quoting indygreg/PyOxidizer#324 (comment), a possible design is (credit @wkschwartz)

use std::mem::MaybeUninit;  
impl PyPreConfig {
  /// Create a new instance initialized with the [Python configuration].
  ///
  /// [Python configuration]: https://docs.python.org/3/c-api/init_config.html#init-python-config
  fn python() -> Self {
    let mut ppc = MaybeUninit::uninit();
    unsafe {
      PyPreConfig_InitPythonConfig(ppc.as_mut_ptr());
      ppc.assume_init()
    }
  }

  /// Create a new instance initialized with the [isolated configuration].
  ///
  /// [isolated configuration]: https://docs.python.org/3/c-api/init_config.html#isolated-configuration
  fn isolated() -> Self {
    let mut ppc = MaybeUninit::uninit();
    unsafe {
      PyPreConfig_InitIsolatedConfig(ppc.as_mut_ptr());
      ppc.assume_init()
    }
  }
}

I have a few notes on this design before we commit to this:

  1. After a user creates one of these FFI as per structs, they will often still need to make use of the unsafe helpers such as PyConfig_SetBytesString to further configure the fields contained. Should we also provide safe APIs for these?
  2. We recently split out the auto-initialize feature, asking users to call prepare_freethreaded_python() directly if they needed. That API doesn't offer a way to tie in these ffi structures. If we are implementing some safe wrappers for the PEP 587 APIs in PyO3, I think it would be nice to go the whole way so that users can have a safe alternative to prepare_freethreaded_python().
  3. Once done, users should call PyConfig_Clear to clean up the allocations contained. Sounds like a perfect candidate for an impl Drop.

Points 1 and 3 particular make me think that instead of adding the methods provided above, we could consider adding a module pyo3::init, which contains safe wrappers roughly along the lines of the following:

pub fn initialize_python_from_config(config: &PyConfig) {
    unsafe { ffi::Py_InitializeFromConfig(config); }
}

pub struct PyConfig(ffi::PyConfig);

impl PyConfig {
    // contains the ::isolated() and ::python() methods above, as well as other safe helpers to set all the fields
}

impl Drop for PyConfig {
    fn drop(&mut self) {
        unsafe { ffi::PyConfig_Clear(&mut self.0); }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions