Skip to content

Commit 9ab7225

Browse files
authored
Merge pull request #1083 from MoritzLangenstein/fallible_py_bytes_bytearray_new_with
Changed PyByte::new_init and PyByteArray::new_init such that init can fail
2 parents 565e36d + e6dc4b2 commit 9ab7225

File tree

2 files changed

+77
-35
lines changed

2 files changed

+77
-35
lines changed

src/types/bytearray.rs

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) 2017-present PyO3 Project and Contributors
22
use crate::err::{PyErr, PyResult};
33
use crate::instance::PyNativeType;
4-
use crate::{ffi, AsPyPointer, PyAny, Python};
4+
use crate::{ffi, AsPyPointer, Py, PyAny, Python};
55
use std::os::raw::c_char;
66
use std::slice;
77

@@ -23,36 +23,40 @@ impl PyByteArray {
2323

2424
/// Creates a new Python `bytearray` object with an `init` closure to write its contents.
2525
/// Before calling `init` the bytearray is zero-initialised.
26-
///
27-
/// Panics if out of memory.
26+
/// * If Python raises a MemoryError on the allocation, `new_with` will return
27+
/// it inside `Err`.
28+
/// * If `init` returns `Err(e)`, `new_with` will return `Err(e)`.
29+
/// * If `init` returns `Ok(())`, `new_with` will return `Ok(&PyByteArray)`.
2830
///
2931
/// # Example
3032
/// ```
3133
/// use pyo3::{prelude::*, types::PyByteArray};
32-
/// Python::with_gil(|py| {
34+
/// Python::with_gil(|py| -> PyResult<()> {
3335
/// let py_bytearray = PyByteArray::new_with(py, 10, |bytes: &mut [u8]| {
3436
/// bytes.copy_from_slice(b"Hello Rust");
35-
/// });
37+
/// Ok(())
38+
/// })?;
3639
/// let bytearray: &[u8] = unsafe { py_bytearray.as_bytes() };
3740
/// assert_eq!(bytearray, b"Hello Rust");
41+
/// Ok(())
3842
/// });
3943
/// ```
40-
pub fn new_with<F>(py: Python, len: usize, init: F) -> &PyByteArray
44+
pub fn new_with<F>(py: Python, len: usize, init: F) -> PyResult<&PyByteArray>
4145
where
42-
F: FnOnce(&mut [u8]),
46+
F: FnOnce(&mut [u8]) -> PyResult<()>,
4347
{
4448
unsafe {
45-
let length = len as ffi::Py_ssize_t;
46-
let pyptr = ffi::PyByteArray_FromStringAndSize(std::ptr::null(), length);
47-
// Iff pyptr is null, py.from_owned_ptr(pyptr) will panic
48-
let pybytearray = py.from_owned_ptr(pyptr);
49+
let pyptr =
50+
ffi::PyByteArray_FromStringAndSize(std::ptr::null(), len as ffi::Py_ssize_t);
51+
// Check for an allocation error and return it
52+
let pypybytearray: Py<PyByteArray> = Py::from_owned_ptr_or_err(py, pyptr)?;
4953
let buffer = ffi::PyByteArray_AsString(pyptr) as *mut u8;
5054
debug_assert!(!buffer.is_null());
5155
// Zero-initialise the uninitialised bytearray
5256
std::ptr::write_bytes(buffer, 0u8, len);
5357
// (Further) Initialise the bytearray in init
54-
init(std::slice::from_raw_parts_mut(buffer, len));
55-
pybytearray
58+
// If init returns an Err, pypybytearray will automatically deallocate the buffer
59+
init(std::slice::from_raw_parts_mut(buffer, len)).map(|_| pypybytearray.into_ref(py))
5660
}
5761
}
5862

@@ -263,22 +267,40 @@ mod test {
263267
}
264268

265269
#[test]
266-
fn test_byte_array_new_with() {
270+
fn test_byte_array_new_with() -> super::PyResult<()> {
267271
let gil = Python::acquire_gil();
268272
let py = gil.python();
269273
let py_bytearray = PyByteArray::new_with(py, 10, |b: &mut [u8]| {
270274
b.copy_from_slice(b"Hello Rust");
271-
});
275+
Ok(())
276+
})?;
272277
let bytearray: &[u8] = unsafe { py_bytearray.as_bytes() };
273278
assert_eq!(bytearray, b"Hello Rust");
279+
Ok(())
274280
}
275281

276282
#[test]
277-
fn test_byte_array_new_with_zero_initialised() {
283+
fn test_byte_array_new_with_zero_initialised() -> super::PyResult<()> {
278284
let gil = Python::acquire_gil();
279285
let py = gil.python();
280-
let py_bytearray = PyByteArray::new_with(py, 10, |_b: &mut [u8]| ());
286+
let py_bytearray = PyByteArray::new_with(py, 10, |_b: &mut [u8]| Ok(()))?;
281287
let bytearray: &[u8] = unsafe { py_bytearray.as_bytes() };
282288
assert_eq!(bytearray, &[0; 10]);
289+
Ok(())
290+
}
291+
292+
#[test]
293+
fn test_byte_array_new_with_error() {
294+
use crate::exceptions::PyValueError;
295+
let gil = Python::acquire_gil();
296+
let py = gil.python();
297+
let py_bytearray_result = PyByteArray::new_with(py, 10, |_b: &mut [u8]| {
298+
Err(PyValueError::py_err("Hello Crustaceans!"))
299+
});
300+
assert!(py_bytearray_result.is_err());
301+
assert!(py_bytearray_result
302+
.err()
303+
.unwrap()
304+
.is_instance::<PyValueError>(py));
283305
}
284306
}

src/types/bytes.rs

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyObject, PyResult, PyTryFrom, Python,
2+
ffi, AsPyPointer, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, PyTryFrom, Python,
33
ToPyObject,
44
};
55
use std::ops::Index;
@@ -28,37 +28,39 @@ impl PyBytes {
2828

2929
/// Creates a new Python `bytes` object with an `init` closure to write its contents.
3030
/// Before calling `init` the bytes' contents are zero-initialised.
31-
///
32-
/// Panics if out of memory.
31+
/// * If Python raises a MemoryError on the allocation, `new_with` will return
32+
/// it inside `Err`.
33+
/// * If `init` returns `Err(e)`, `new_with` will return `Err(e)`.
34+
/// * If `init` returns `Ok(())`, `new_with` will return `Ok(&PyBytes)`.
3335
///
3436
/// # Example
3537
/// ```
3638
/// use pyo3::{prelude::*, types::PyBytes};
3739
/// Python::with_gil(|py| -> PyResult<()> {
3840
/// let py_bytes = PyBytes::new_with(py, 10, |bytes: &mut [u8]| {
3941
/// bytes.copy_from_slice(b"Hello Rust");
40-
/// });
42+
/// Ok(())
43+
/// })?;
4144
/// let bytes: &[u8] = FromPyObject::extract(py_bytes)?;
4245
/// assert_eq!(bytes, b"Hello Rust");
4346
/// Ok(())
4447
/// });
4548
/// ```
46-
pub fn new_with<F>(py: Python, len: usize, init: F) -> &PyBytes
49+
pub fn new_with<F>(py: Python, len: usize, init: F) -> PyResult<&PyBytes>
4750
where
48-
F: FnOnce(&mut [u8]),
51+
F: FnOnce(&mut [u8]) -> PyResult<()>,
4952
{
5053
unsafe {
51-
let length = len as ffi::Py_ssize_t;
52-
let pyptr = ffi::PyBytes_FromStringAndSize(std::ptr::null(), length);
53-
// Iff pyptr is null, py.from_owned_ptr(pyptr) will panic
54-
let pybytes = py.from_owned_ptr(pyptr);
54+
let pyptr = ffi::PyBytes_FromStringAndSize(std::ptr::null(), len as ffi::Py_ssize_t);
55+
// Check for an allocation error and return it
56+
let pypybytes: Py<PyBytes> = Py::from_owned_ptr_or_err(py, pyptr)?;
5557
let buffer = ffi::PyBytes_AsString(pyptr) as *mut u8;
5658
debug_assert!(!buffer.is_null());
5759
// Zero-initialise the uninitialised bytestring
5860
std::ptr::write_bytes(buffer, 0u8, len);
5961
// (Further) Initialise the bytestring in init
60-
init(std::slice::from_raw_parts_mut(buffer, len));
61-
pybytes
62+
// If init returns an Err, pypybytearray will automatically deallocate the buffer
63+
init(std::slice::from_raw_parts_mut(buffer, len)).map(|_| pypybytes.into_ref(py))
6264
}
6365
}
6466

@@ -129,22 +131,40 @@ mod test {
129131
}
130132

131133
#[test]
132-
fn test_bytes_new_with() {
134+
fn test_bytes_new_with() -> super::PyResult<()> {
133135
let gil = Python::acquire_gil();
134136
let py = gil.python();
135137
let py_bytes = PyBytes::new_with(py, 10, |b: &mut [u8]| {
136138
b.copy_from_slice(b"Hello Rust");
137-
});
138-
let bytes: &[u8] = FromPyObject::extract(py_bytes).unwrap();
139+
Ok(())
140+
})?;
141+
let bytes: &[u8] = FromPyObject::extract(py_bytes)?;
139142
assert_eq!(bytes, b"Hello Rust");
143+
Ok(())
140144
}
141145

142146
#[test]
143-
fn test_bytes_new_with_zero_initialised() {
147+
fn test_bytes_new_with_zero_initialised() -> super::PyResult<()> {
144148
let gil = Python::acquire_gil();
145149
let py = gil.python();
146-
let py_bytes = PyBytes::new_with(py, 10, |_b: &mut [u8]| ());
147-
let bytes: &[u8] = FromPyObject::extract(py_bytes).unwrap();
150+
let py_bytes = PyBytes::new_with(py, 10, |_b: &mut [u8]| Ok(()))?;
151+
let bytes: &[u8] = FromPyObject::extract(py_bytes)?;
148152
assert_eq!(bytes, &[0; 10]);
153+
Ok(())
154+
}
155+
156+
#[test]
157+
fn test_bytes_new_with_error() {
158+
use crate::exceptions::PyValueError;
159+
let gil = Python::acquire_gil();
160+
let py = gil.python();
161+
let py_bytes_result = PyBytes::new_with(py, 10, |_b: &mut [u8]| {
162+
Err(PyValueError::py_err("Hello Crustaceans!"))
163+
});
164+
assert!(py_bytes_result.is_err());
165+
assert!(py_bytes_result
166+
.err()
167+
.unwrap()
168+
.is_instance::<PyValueError>(py));
149169
}
150170
}

0 commit comments

Comments
 (0)