Skip to content

Commit

Permalink
add c_str! macro to create &'static CStr (#4255)
Browse files Browse the repository at this point in the history
* add `c_str!` macro to create `&'static CStr`

* newsfragment, just export as `pyo3::ffi::c_str`

* fix doc link

* fix doc

* further `c_str!` based cleanups

* [review]: mejrs

Co-authored-by: Bruno Kolenbrander <59372212+mejrs@users.noreply.github.com>

* rustfmt

* build fixes

* clippy

* allow lint on MSRV

* fix GraalPy import

---------

Co-authored-by: Bruno Kolenbrander <59372212+mejrs@users.noreply.github.com>
  • Loading branch information
davidhewitt and mejrs authored Jun 18, 2024
1 parent ddff8be commit 0e142f0
Show file tree
Hide file tree
Showing 36 changed files with 357 additions and 416 deletions.
21 changes: 13 additions & 8 deletions examples/sequential/src/id.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use core::sync::atomic::{AtomicU64, Ordering};
use core::{mem, ptr};
use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_uint, c_ulonglong, c_void};

use pyo3_ffi::*;
Expand Down Expand Up @@ -27,10 +28,10 @@ unsafe extern "C" fn id_new(
kwds: *mut PyObject,
) -> *mut PyObject {
if PyTuple_Size(args) != 0 || !kwds.is_null() {
PyErr_SetString(
PyExc_TypeError,
"Id() takes no arguments\0".as_ptr().cast::<c_char>(),
);
// We use pyo3-ffi's `c_str!` macro to create null-terminated literals because
// Rust's string literals are not null-terminated
// On Rust 1.77 or newer you can use `c"text"` instead.
PyErr_SetString(PyExc_TypeError, c_str!("Id() takes no arguments").as_ptr());
return ptr::null_mut();
}

Expand Down Expand Up @@ -81,8 +82,12 @@ unsafe extern "C" fn id_richcompare(
pyo3_ffi::Py_GT => slf > other,
pyo3_ffi::Py_GE => slf >= other,
unrecognized => {
let msg = format!("unrecognized richcompare opcode {}\0", unrecognized);
PyErr_SetString(PyExc_SystemError, msg.as_ptr().cast::<c_char>());
let msg = CString::new(&*format!(
"unrecognized richcompare opcode {}",
unrecognized
))
.unwrap();
PyErr_SetString(PyExc_SystemError, msg.as_ptr());
return ptr::null_mut();
}
};
Expand All @@ -101,7 +106,7 @@ static mut SLOTS: &[PyType_Slot] = &[
},
PyType_Slot {
slot: Py_tp_doc,
pfunc: "An id that is increased every time an instance is created\0".as_ptr()
pfunc: c_str!("An id that is increased every time an instance is created").as_ptr()
as *mut c_void,
},
PyType_Slot {
Expand All @@ -123,7 +128,7 @@ static mut SLOTS: &[PyType_Slot] = &[
];

pub static mut ID_SPEC: PyType_Spec = PyType_Spec {
name: "sequential.Id\0".as_ptr().cast::<c_char>(),
name: c_str!("sequential.Id").as_ptr(),
basicsize: mem::size_of::<PyId>() as c_int,
itemsize: 0,
flags: (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE) as c_uint,
Expand Down
12 changes: 5 additions & 7 deletions examples/sequential/src/module.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use core::{mem, ptr};
use pyo3_ffi::*;
use std::os::raw::{c_char, c_int, c_void};
use std::os::raw::{c_int, c_void};

pub static mut MODULE_DEF: PyModuleDef = PyModuleDef {
m_base: PyModuleDef_HEAD_INIT,
m_name: "sequential\0".as_ptr().cast::<c_char>(),
m_doc: "A library for generating sequential ids, written in Rust.\0"
.as_ptr()
.cast::<c_char>(),
m_name: c_str!("sequential").as_ptr(),
m_doc: c_str!("A library for generating sequential ids, written in Rust.").as_ptr(),
m_size: mem::size_of::<sequential_state>() as Py_ssize_t,
m_methods: std::ptr::null_mut(),
m_slots: unsafe { SEQUENTIAL_SLOTS as *const [PyModuleDef_Slot] as *mut PyModuleDef_Slot },
Expand Down Expand Up @@ -42,13 +40,13 @@ unsafe extern "C" fn sequential_exec(module: *mut PyObject) -> c_int {
if id_type.is_null() {
PyErr_SetString(
PyExc_SystemError,
"cannot locate type object\0".as_ptr().cast::<c_char>(),
c_str!("cannot locate type object").as_ptr(),
);
return -1;
}
(*state).id_type = id_type.cast::<PyTypeObject>();

PyModule_AddObjectRef(module, "Id\0".as_ptr().cast::<c_char>(), id_type)
PyModule_AddObjectRef(module, c_str!("Id").as_ptr(), id_type)
}

unsafe extern "C" fn sequential_traverse(
Expand Down
20 changes: 8 additions & 12 deletions examples/sequential/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ use std::thread;
use pyo3_ffi::*;
use sequential::PyInit_sequential;

static COMMAND: &'static str = "
static COMMAND: &'static str = c_str!(
"
from sequential import Id
s = sum(int(Id()) for _ in range(12))
\0";
"
);

// Newtype to be able to pass it to another thread.
struct State(*mut PyThreadState);
Expand All @@ -19,10 +21,7 @@ unsafe impl Send for State {}
#[test]
fn lets_go_fast() -> Result<(), String> {
unsafe {
let ret = PyImport_AppendInittab(
"sequential\0".as_ptr().cast::<c_char>(),
Some(PyInit_sequential),
);
let ret = PyImport_AppendInittab(c_str!("sequential").as_ptr(), Some(PyInit_sequential));
if ret == -1 {
return Err("could not add module to inittab".into());
}
Expand Down Expand Up @@ -122,11 +121,8 @@ unsafe fn fetch() -> String {

fn run_code() -> Result<u64, String> {
unsafe {
let code_obj = Py_CompileString(
COMMAND.as_ptr().cast::<c_char>(),
"program\0".as_ptr().cast::<c_char>(),
Py_file_input,
);
let code_obj =
Py_CompileString(COMMAND.as_ptr(), c_str!("program").as_ptr(), Py_file_input);
if code_obj.is_null() {
return Err(fetch());
}
Expand All @@ -138,7 +134,7 @@ fn run_code() -> Result<u64, String> {
} else {
Py_DECREF(res_ptr);
}
let sum = PyDict_GetItemString(globals, "s\0".as_ptr().cast::<c_char>()); /* borrowed reference */
let sum = PyDict_GetItemString(globals, c_str!("s").as_ptr()); /* borrowed reference */
if sum.is_null() {
Py_DECREF(globals);
return Err("globals did not have `s`".into());
Expand Down
18 changes: 6 additions & 12 deletions examples/string-sum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ use pyo3_ffi::*;

static mut MODULE_DEF: PyModuleDef = PyModuleDef {
m_base: PyModuleDef_HEAD_INIT,
m_name: "string_sum\0".as_ptr().cast::<c_char>(),
m_doc: "A Python module written in Rust.\0"
.as_ptr()
.cast::<c_char>(),
m_name: c_str!("string_sum").as_ptr(),
m_doc: c_str!("A Python module written in Rust.").as_ptr(),
m_size: 0,
m_methods: unsafe { METHODS as *const [PyMethodDef] as *mut PyMethodDef },
m_slots: std::ptr::null_mut(),
Expand All @@ -19,14 +17,12 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {

static mut METHODS: &[PyMethodDef] = &[
PyMethodDef {
ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(),
ml_name: c_str!("sum_as_string").as_ptr(),
ml_meth: PyMethodDefPointer {
_PyCFunctionFast: sum_as_string,
},
ml_flags: METH_FASTCALL,
ml_doc: "returns the sum of two integers as a string\0"
.as_ptr()
.cast::<c_char>(),
ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
},
// A zeroed PyMethodDef to mark the end of the array.
PyMethodDef::zeroed(),
Expand Down Expand Up @@ -93,9 +89,7 @@ pub unsafe extern "C" fn sum_as_string(
if nargs != 2 {
PyErr_SetString(
PyExc_TypeError,
"sum_as_string expected 2 positional arguments\0"
.as_ptr()
.cast::<c_char>(),
c_str!("sum_as_string expected 2 positional arguments").as_ptr(),
);
return std::ptr::null_mut();
}
Expand All @@ -119,7 +113,7 @@ pub unsafe extern "C" fn sum_as_string(
None => {
PyErr_SetString(
PyExc_OverflowError,
"arguments too large to add\0".as_ptr().cast::<c_char>(),
c_str!("arguments too large to add").as_ptr(),
);
std::ptr::null_mut()
}
Expand Down
10 changes: 5 additions & 5 deletions guide/src/class.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,8 @@ or [`PyRefMut`] instead of `&mut self`.
Then you can access a parent class by `self_.as_super()` as `&PyRef<Self::BaseClass>`,
or by `self_.into_super()` as `PyRef<Self::BaseClass>` (and similar for the `PyRefMut`
case). For convenience, `self_.as_ref()` can also be used to get `&Self::BaseClass`
directly; however, this approach does not let you access base clases higher in the
inheritance hierarchy, for which you would need to chain multiple `as_super` or
directly; however, this approach does not let you access base clases higher in the
inheritance hierarchy, for which you would need to chain multiple `as_super` or
`into_super` calls.

```rust
Expand Down Expand Up @@ -400,7 +400,7 @@ impl SubSubClass {
let val2 = self_.as_super().val2;
(val1, val2, self_.val3)
}

fn double_values(mut self_: PyRefMut<'_, Self>) {
self_.as_super().as_super().val1 *= 2;
self_.as_super().val2 *= 2;
Expand Down Expand Up @@ -1187,7 +1187,7 @@ Python::with_gil(|py| {
})
```

Ordering of enum variants is optionally added using `#[pyo3(ord)]`.
Ordering of enum variants is optionally added using `#[pyo3(ord)]`.
*Note: Implementation of the `PartialOrd` trait is required when passing the `ord` argument. If not implemented, a compile time error is raised.*

```rust
Expand Down Expand Up @@ -1443,7 +1443,7 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
static DOC: pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = pyo3::sync::GILOnceCell::new();
DOC.get_or_try_init(py, || {
let collector = PyClassImplCollector::<Self>::new();
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, "\0", collector.new_text_signature())
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, pyo3::ffi::c_str!(""), collector.new_text_signature())
}).map(::std::ops::Deref::deref)
}
}
Expand Down
1 change: 1 addition & 0 deletions newsfragments/4255.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `pyo3_ffi::c_str` macro to create `&'static CStr` on Rust versions which don't have 1.77's `c""` literals.
1 change: 1 addition & 0 deletions newsfragments/4255.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`PyCFunction::new`, `PyCFunction::new_with_keywords` and `PyCFunction::new_closure` now take `&'static CStr` name and doc arguments (previously was `&'static str`).
26 changes: 8 additions & 18 deletions pyo3-ffi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,8 @@ use pyo3_ffi::*;

static mut MODULE_DEF: PyModuleDef = PyModuleDef {
m_base: PyModuleDef_HEAD_INIT,
m_name: "string_sum\0".as_ptr().cast::<c_char>(),
m_doc: "A Python module written in Rust.\0"
.as_ptr()
.cast::<c_char>(),
m_name: c_str!("string_sum").as_ptr(),
m_doc: c_str!("A Python module written in Rust.").as_ptr(),
m_size: 0,
m_methods: unsafe { METHODS.as_mut_ptr().cast() },
m_slots: std::ptr::null_mut(),
Expand All @@ -65,14 +63,12 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {

static mut METHODS: [PyMethodDef; 2] = [
PyMethodDef {
ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(),
ml_name: c_str!("sum_as_string").as_ptr(),
ml_meth: PyMethodDefPointer {
_PyCFunctionFast: sum_as_string,
},
ml_flags: METH_FASTCALL,
ml_doc: "returns the sum of two integers as a string\0"
.as_ptr()
.cast::<c_char>(),
ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
},
// A zeroed PyMethodDef to mark the end of the array.
PyMethodDef::zeroed()
Expand All @@ -93,9 +89,7 @@ pub unsafe extern "C" fn sum_as_string(
if nargs != 2 {
PyErr_SetString(
PyExc_TypeError,
"sum_as_string() expected 2 positional arguments\0"
.as_ptr()
.cast::<c_char>(),
c_str!("sum_as_string() expected 2 positional arguments").as_ptr(),
);
return std::ptr::null_mut();
}
Expand All @@ -104,9 +98,7 @@ pub unsafe extern "C" fn sum_as_string(
if PyLong_Check(arg1) == 0 {
PyErr_SetString(
PyExc_TypeError,
"sum_as_string() expected an int for positional argument 1\0"
.as_ptr()
.cast::<c_char>(),
c_str!("sum_as_string() expected an int for positional argument 1").as_ptr(),
);
return std::ptr::null_mut();
}
Expand All @@ -120,9 +112,7 @@ pub unsafe extern "C" fn sum_as_string(
if PyLong_Check(arg2) == 0 {
PyErr_SetString(
PyExc_TypeError,
"sum_as_string() expected an int for positional argument 2\0"
.as_ptr()
.cast::<c_char>(),
c_str!("sum_as_string() expected an int for positional argument 2").as_ptr(),
);
return std::ptr::null_mut();
}
Expand All @@ -140,7 +130,7 @@ pub unsafe extern "C" fn sum_as_string(
None => {
PyErr_SetString(
PyExc_OverflowError,
"arguments too large to add\0".as_ptr().cast::<c_char>(),
c_str!("arguments too large to add").as_ptr(),
);
std::ptr::null_mut()
}
Expand Down
2 changes: 1 addition & 1 deletion pyo3-ffi/src/abstract_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ extern "C" {
#[cfg(not(any(Py_3_8, PyPy)))]
#[inline]
pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int {
crate::PyObject_HasAttrString(crate::Py_TYPE(o).cast(), "__next__\0".as_ptr().cast())
crate::PyObject_HasAttrString(crate::Py_TYPE(o).cast(), c_str!("__next__").as_ptr())
}

extern "C" {
Expand Down
Loading

0 comments on commit 0e142f0

Please sign in to comment.