Skip to content

Commit 0e142f0

Browse files
davidhewittmejrs
andauthored
add c_str! macro to create &'static CStr (#4255)
* 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 <[email protected]> * rustfmt * build fixes * clippy * allow lint on MSRV * fix GraalPy import --------- Co-authored-by: Bruno Kolenbrander <[email protected]>
1 parent ddff8be commit 0e142f0

36 files changed

+357
-416
lines changed

examples/sequential/src/id.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use core::sync::atomic::{AtomicU64, Ordering};
22
use core::{mem, ptr};
3+
use std::ffi::CString;
34
use std::os::raw::{c_char, c_int, c_uint, c_ulonglong, c_void};
45

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

@@ -81,8 +82,12 @@ unsafe extern "C" fn id_richcompare(
8182
pyo3_ffi::Py_GT => slf > other,
8283
pyo3_ffi::Py_GE => slf >= other,
8384
unrecognized => {
84-
let msg = format!("unrecognized richcompare opcode {}\0", unrecognized);
85-
PyErr_SetString(PyExc_SystemError, msg.as_ptr().cast::<c_char>());
85+
let msg = CString::new(&*format!(
86+
"unrecognized richcompare opcode {}",
87+
unrecognized
88+
))
89+
.unwrap();
90+
PyErr_SetString(PyExc_SystemError, msg.as_ptr());
8691
return ptr::null_mut();
8792
}
8893
};
@@ -101,7 +106,7 @@ static mut SLOTS: &[PyType_Slot] = &[
101106
},
102107
PyType_Slot {
103108
slot: Py_tp_doc,
104-
pfunc: "An id that is increased every time an instance is created\0".as_ptr()
109+
pfunc: c_str!("An id that is increased every time an instance is created").as_ptr()
105110
as *mut c_void,
106111
},
107112
PyType_Slot {
@@ -123,7 +128,7 @@ static mut SLOTS: &[PyType_Slot] = &[
123128
];
124129

125130
pub static mut ID_SPEC: PyType_Spec = PyType_Spec {
126-
name: "sequential.Id\0".as_ptr().cast::<c_char>(),
131+
name: c_str!("sequential.Id").as_ptr(),
127132
basicsize: mem::size_of::<PyId>() as c_int,
128133
itemsize: 0,
129134
flags: (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE) as c_uint,

examples/sequential/src/module.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
use core::{mem, ptr};
22
use pyo3_ffi::*;
3-
use std::os::raw::{c_char, c_int, c_void};
3+
use std::os::raw::{c_int, c_void};
44

55
pub static mut MODULE_DEF: PyModuleDef = PyModuleDef {
66
m_base: PyModuleDef_HEAD_INIT,
7-
m_name: "sequential\0".as_ptr().cast::<c_char>(),
8-
m_doc: "A library for generating sequential ids, written in Rust.\0"
9-
.as_ptr()
10-
.cast::<c_char>(),
7+
m_name: c_str!("sequential").as_ptr(),
8+
m_doc: c_str!("A library for generating sequential ids, written in Rust.").as_ptr(),
119
m_size: mem::size_of::<sequential_state>() as Py_ssize_t,
1210
m_methods: std::ptr::null_mut(),
1311
m_slots: unsafe { SEQUENTIAL_SLOTS as *const [PyModuleDef_Slot] as *mut PyModuleDef_Slot },
@@ -42,13 +40,13 @@ unsafe extern "C" fn sequential_exec(module: *mut PyObject) -> c_int {
4240
if id_type.is_null() {
4341
PyErr_SetString(
4442
PyExc_SystemError,
45-
"cannot locate type object\0".as_ptr().cast::<c_char>(),
43+
c_str!("cannot locate type object").as_ptr(),
4644
);
4745
return -1;
4846
}
4947
(*state).id_type = id_type.cast::<PyTypeObject>();
5048

51-
PyModule_AddObjectRef(module, "Id\0".as_ptr().cast::<c_char>(), id_type)
49+
PyModule_AddObjectRef(module, c_str!("Id").as_ptr(), id_type)
5250
}
5351

5452
unsafe extern "C" fn sequential_traverse(

examples/sequential/tests/test.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use std::thread;
55
use pyo3_ffi::*;
66
use sequential::PyInit_sequential;
77

8-
static COMMAND: &'static str = "
8+
static COMMAND: &'static str = c_str!(
9+
"
910
from sequential import Id
1011
1112
s = sum(int(Id()) for _ in range(12))
12-
\0";
13+
"
14+
);
1315

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

123122
fn run_code() -> Result<u64, String> {
124123
unsafe {
125-
let code_obj = Py_CompileString(
126-
COMMAND.as_ptr().cast::<c_char>(),
127-
"program\0".as_ptr().cast::<c_char>(),
128-
Py_file_input,
129-
);
124+
let code_obj =
125+
Py_CompileString(COMMAND.as_ptr(), c_str!("program").as_ptr(), Py_file_input);
130126
if code_obj.is_null() {
131127
return Err(fetch());
132128
}
@@ -138,7 +134,7 @@ fn run_code() -> Result<u64, String> {
138134
} else {
139135
Py_DECREF(res_ptr);
140136
}
141-
let sum = PyDict_GetItemString(globals, "s\0".as_ptr().cast::<c_char>()); /* borrowed reference */
137+
let sum = PyDict_GetItemString(globals, c_str!("s").as_ptr()); /* borrowed reference */
142138
if sum.is_null() {
143139
Py_DECREF(globals);
144140
return Err("globals did not have `s`".into());

examples/string-sum/src/lib.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ use pyo3_ffi::*;
55

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

2018
static mut METHODS: &[PyMethodDef] = &[
2119
PyMethodDef {
22-
ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(),
20+
ml_name: c_str!("sum_as_string").as_ptr(),
2321
ml_meth: PyMethodDefPointer {
2422
_PyCFunctionFast: sum_as_string,
2523
},
2624
ml_flags: METH_FASTCALL,
27-
ml_doc: "returns the sum of two integers as a string\0"
28-
.as_ptr()
29-
.cast::<c_char>(),
25+
ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
3026
},
3127
// A zeroed PyMethodDef to mark the end of the array.
3228
PyMethodDef::zeroed(),
@@ -93,9 +89,7 @@ pub unsafe extern "C" fn sum_as_string(
9389
if nargs != 2 {
9490
PyErr_SetString(
9591
PyExc_TypeError,
96-
"sum_as_string expected 2 positional arguments\0"
97-
.as_ptr()
98-
.cast::<c_char>(),
92+
c_str!("sum_as_string expected 2 positional arguments").as_ptr(),
9993
);
10094
return std::ptr::null_mut();
10195
}
@@ -119,7 +113,7 @@ pub unsafe extern "C" fn sum_as_string(
119113
None => {
120114
PyErr_SetString(
121115
PyExc_OverflowError,
122-
"arguments too large to add\0".as_ptr().cast::<c_char>(),
116+
c_str!("arguments too large to add").as_ptr(),
123117
);
124118
std::ptr::null_mut()
125119
}

guide/src/class.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,8 @@ or [`PyRefMut`] instead of `&mut self`.
330330
Then you can access a parent class by `self_.as_super()` as `&PyRef<Self::BaseClass>`,
331331
or by `self_.into_super()` as `PyRef<Self::BaseClass>` (and similar for the `PyRefMut`
332332
case). For convenience, `self_.as_ref()` can also be used to get `&Self::BaseClass`
333-
directly; however, this approach does not let you access base clases higher in the
334-
inheritance hierarchy, for which you would need to chain multiple `as_super` or
333+
directly; however, this approach does not let you access base clases higher in the
334+
inheritance hierarchy, for which you would need to chain multiple `as_super` or
335335
`into_super` calls.
336336

337337
```rust
@@ -400,7 +400,7 @@ impl SubSubClass {
400400
let val2 = self_.as_super().val2;
401401
(val1, val2, self_.val3)
402402
}
403-
403+
404404
fn double_values(mut self_: PyRefMut<'_, Self>) {
405405
self_.as_super().as_super().val1 *= 2;
406406
self_.as_super().val2 *= 2;
@@ -1187,7 +1187,7 @@ Python::with_gil(|py| {
11871187
})
11881188
```
11891189

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

11931193
```rust
@@ -1443,7 +1443,7 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
14431443
static DOC: pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = pyo3::sync::GILOnceCell::new();
14441444
DOC.get_or_try_init(py, || {
14451445
let collector = PyClassImplCollector::<Self>::new();
1446-
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, "\0", collector.new_text_signature())
1446+
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, pyo3::ffi::c_str!(""), collector.new_text_signature())
14471447
}).map(::std::ops::Deref::deref)
14481448
}
14491449
}

newsfragments/4255.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `pyo3_ffi::c_str` macro to create `&'static CStr` on Rust versions which don't have 1.77's `c""` literals.

newsfragments/4255.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`PyCFunction::new`, `PyCFunction::new_with_keywords` and `PyCFunction::new_closure` now take `&'static CStr` name and doc arguments (previously was `&'static str`).

pyo3-ffi/README.md

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,8 @@ use pyo3_ffi::*;
5151

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

6664
static mut METHODS: [PyMethodDef; 2] = [
6765
PyMethodDef {
68-
ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(),
66+
ml_name: c_str!("sum_as_string").as_ptr(),
6967
ml_meth: PyMethodDefPointer {
7068
_PyCFunctionFast: sum_as_string,
7169
},
7270
ml_flags: METH_FASTCALL,
73-
ml_doc: "returns the sum of two integers as a string\0"
74-
.as_ptr()
75-
.cast::<c_char>(),
71+
ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
7672
},
7773
// A zeroed PyMethodDef to mark the end of the array.
7874
PyMethodDef::zeroed()
@@ -93,9 +89,7 @@ pub unsafe extern "C" fn sum_as_string(
9389
if nargs != 2 {
9490
PyErr_SetString(
9591
PyExc_TypeError,
96-
"sum_as_string() expected 2 positional arguments\0"
97-
.as_ptr()
98-
.cast::<c_char>(),
92+
c_str!("sum_as_string() expected 2 positional arguments").as_ptr(),
9993
);
10094
return std::ptr::null_mut();
10195
}
@@ -104,9 +98,7 @@ pub unsafe extern "C" fn sum_as_string(
10498
if PyLong_Check(arg1) == 0 {
10599
PyErr_SetString(
106100
PyExc_TypeError,
107-
"sum_as_string() expected an int for positional argument 1\0"
108-
.as_ptr()
109-
.cast::<c_char>(),
101+
c_str!("sum_as_string() expected an int for positional argument 1").as_ptr(),
110102
);
111103
return std::ptr::null_mut();
112104
}
@@ -120,9 +112,7 @@ pub unsafe extern "C" fn sum_as_string(
120112
if PyLong_Check(arg2) == 0 {
121113
PyErr_SetString(
122114
PyExc_TypeError,
123-
"sum_as_string() expected an int for positional argument 2\0"
124-
.as_ptr()
125-
.cast::<c_char>(),
115+
c_str!("sum_as_string() expected an int for positional argument 2").as_ptr(),
126116
);
127117
return std::ptr::null_mut();
128118
}
@@ -140,7 +130,7 @@ pub unsafe extern "C" fn sum_as_string(
140130
None => {
141131
PyErr_SetString(
142132
PyExc_OverflowError,
143-
"arguments too large to add\0".as_ptr().cast::<c_char>(),
133+
c_str!("arguments too large to add").as_ptr(),
144134
);
145135
std::ptr::null_mut()
146136
}

pyo3-ffi/src/abstract_.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ extern "C" {
114114
#[cfg(not(any(Py_3_8, PyPy)))]
115115
#[inline]
116116
pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int {
117-
crate::PyObject_HasAttrString(crate::Py_TYPE(o).cast(), "__next__\0".as_ptr().cast())
117+
crate::PyObject_HasAttrString(crate::Py_TYPE(o).cast(), c_str!("__next__").as_ptr())
118118
}
119119

120120
extern "C" {

0 commit comments

Comments
 (0)