Skip to content

Changed PyByte::new_init and PyByteArray::new_init such that init can fail #1083

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 39 additions & 17 deletions src/types/bytearray.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::err::{PyErr, PyResult};
use crate::instance::PyNativeType;
use crate::{ffi, AsPyPointer, PyAny, Python};
use crate::{ffi, AsPyPointer, Py, PyAny, Python};
use std::os::raw::c_char;
use std::slice;

Expand All @@ -23,36 +23,40 @@ impl PyByteArray {

/// Creates a new Python `bytearray` object with an `init` closure to write its contents.
/// Before calling `init` the bytearray is zero-initialised.
///
/// Panics if out of memory.
/// * If Python raises a MemoryError on the allocation, `new_with` will return
/// it inside `Err`.
/// * If `init` returns `Err(e)`, `new_with` will return `Err(e)`.
/// * If `init` returns `Ok(())`, `new_with` will return `Ok(&PyByteArray)`.
///
/// # Example
/// ```
/// use pyo3::{prelude::*, types::PyByteArray};
/// Python::with_gil(|py| {
/// Python::with_gil(|py| -> PyResult<()> {
/// let py_bytearray = PyByteArray::new_with(py, 10, |bytes: &mut [u8]| {
/// bytes.copy_from_slice(b"Hello Rust");
/// });
/// Ok(())
/// })?;
/// let bytearray: &[u8] = unsafe { py_bytearray.as_bytes() };
/// assert_eq!(bytearray, b"Hello Rust");
/// Ok(())
/// });
/// ```
pub fn new_with<F>(py: Python, len: usize, init: F) -> &PyByteArray
pub fn new_with<F>(py: Python, len: usize, init: F) -> PyResult<&PyByteArray>
where
F: FnOnce(&mut [u8]),
F: FnOnce(&mut [u8]) -> PyResult<()>,
{
unsafe {
let length = len as ffi::Py_ssize_t;
let pyptr = ffi::PyByteArray_FromStringAndSize(std::ptr::null(), length);
// Iff pyptr is null, py.from_owned_ptr(pyptr) will panic
let pybytearray = py.from_owned_ptr(pyptr);
let pyptr =
ffi::PyByteArray_FromStringAndSize(std::ptr::null(), len as ffi::Py_ssize_t);
// Check for an allocation error and return it
let pypybytearray: Py<PyByteArray> = Py::from_owned_ptr_or_err(py, pyptr)?;
let buffer = ffi::PyByteArray_AsString(pyptr) as *mut u8;
debug_assert!(!buffer.is_null());
// Zero-initialise the uninitialised bytearray
std::ptr::write_bytes(buffer, 0u8, len);
// (Further) Initialise the bytearray in init
init(std::slice::from_raw_parts_mut(buffer, len));
pybytearray
// If init returns an Err, pypybytearray will automatically deallocate the buffer
init(std::slice::from_raw_parts_mut(buffer, len)).map(|_| pypybytearray.into_ref(py))
}
}

Expand Down Expand Up @@ -263,22 +267,40 @@ mod test {
}

#[test]
fn test_byte_array_new_with() {
fn test_byte_array_new_with() -> super::PyResult<()> {
let gil = Python::acquire_gil();
let py = gil.python();
let py_bytearray = PyByteArray::new_with(py, 10, |b: &mut [u8]| {
b.copy_from_slice(b"Hello Rust");
});
Ok(())
})?;
let bytearray: &[u8] = unsafe { py_bytearray.as_bytes() };
assert_eq!(bytearray, b"Hello Rust");
Ok(())
}

#[test]
fn test_byte_array_new_with_zero_initialised() {
fn test_byte_array_new_with_zero_initialised() -> super::PyResult<()> {
let gil = Python::acquire_gil();
let py = gil.python();
let py_bytearray = PyByteArray::new_with(py, 10, |_b: &mut [u8]| ());
let py_bytearray = PyByteArray::new_with(py, 10, |_b: &mut [u8]| Ok(()))?;
let bytearray: &[u8] = unsafe { py_bytearray.as_bytes() };
assert_eq!(bytearray, &[0; 10]);
Ok(())
}

#[test]
fn test_byte_array_new_with_error() {
use crate::exceptions::PyValueError;
let gil = Python::acquire_gil();
let py = gil.python();
let py_bytearray_result = PyByteArray::new_with(py, 10, |_b: &mut [u8]| {
Err(PyValueError::py_err("Hello Crustaceans!"))
});
assert!(py_bytearray_result.is_err());
assert!(py_bytearray_result
.err()
.unwrap()
.is_instance::<PyValueError>(py));
}
}
56 changes: 38 additions & 18 deletions src/types/bytes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyObject, PyResult, PyTryFrom, Python,
ffi, AsPyPointer, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, PyTryFrom, Python,
ToPyObject,
};
use std::ops::Index;
Expand Down Expand Up @@ -28,37 +28,39 @@ impl PyBytes {

/// Creates a new Python `bytes` object with an `init` closure to write its contents.
/// Before calling `init` the bytes' contents are zero-initialised.
///
/// Panics if out of memory.
/// * If Python raises a MemoryError on the allocation, `new_with` will return
/// it inside `Err`.
/// * If `init` returns `Err(e)`, `new_with` will return `Err(e)`.
/// * If `init` returns `Ok(())`, `new_with` will return `Ok(&PyBytes)`.
///
/// # Example
/// ```
/// use pyo3::{prelude::*, types::PyBytes};
/// Python::with_gil(|py| -> PyResult<()> {
/// let py_bytes = PyBytes::new_with(py, 10, |bytes: &mut [u8]| {
/// bytes.copy_from_slice(b"Hello Rust");
/// });
/// Ok(())
/// })?;
/// let bytes: &[u8] = FromPyObject::extract(py_bytes)?;
/// assert_eq!(bytes, b"Hello Rust");
/// Ok(())
/// });
/// ```
pub fn new_with<F>(py: Python, len: usize, init: F) -> &PyBytes
pub fn new_with<F>(py: Python, len: usize, init: F) -> PyResult<&PyBytes>
where
F: FnOnce(&mut [u8]),
F: FnOnce(&mut [u8]) -> PyResult<()>,
{
unsafe {
let length = len as ffi::Py_ssize_t;
let pyptr = ffi::PyBytes_FromStringAndSize(std::ptr::null(), length);
// Iff pyptr is null, py.from_owned_ptr(pyptr) will panic
let pybytes = py.from_owned_ptr(pyptr);
let pyptr = ffi::PyBytes_FromStringAndSize(std::ptr::null(), len as ffi::Py_ssize_t);
// Check for an allocation error and return it
let pypybytes: Py<PyBytes> = Py::from_owned_ptr_or_err(py, pyptr)?;
let buffer = ffi::PyBytes_AsString(pyptr) as *mut u8;
debug_assert!(!buffer.is_null());
// Zero-initialise the uninitialised bytestring
std::ptr::write_bytes(buffer, 0u8, len);
// (Further) Initialise the bytestring in init
init(std::slice::from_raw_parts_mut(buffer, len));
pybytes
// If init returns an Err, pypybytearray will automatically deallocate the buffer
init(std::slice::from_raw_parts_mut(buffer, len)).map(|_| pypybytes.into_ref(py))
}
}

Expand Down Expand Up @@ -129,22 +131,40 @@ mod test {
}

#[test]
fn test_bytes_new_with() {
fn test_bytes_new_with() -> super::PyResult<()> {
let gil = Python::acquire_gil();
let py = gil.python();
let py_bytes = PyBytes::new_with(py, 10, |b: &mut [u8]| {
b.copy_from_slice(b"Hello Rust");
});
let bytes: &[u8] = FromPyObject::extract(py_bytes).unwrap();
Ok(())
})?;
let bytes: &[u8] = FromPyObject::extract(py_bytes)?;
assert_eq!(bytes, b"Hello Rust");
Ok(())
}

#[test]
fn test_bytes_new_with_zero_initialised() {
fn test_bytes_new_with_zero_initialised() -> super::PyResult<()> {
let gil = Python::acquire_gil();
let py = gil.python();
let py_bytes = PyBytes::new_with(py, 10, |_b: &mut [u8]| ());
let bytes: &[u8] = FromPyObject::extract(py_bytes).unwrap();
let py_bytes = PyBytes::new_with(py, 10, |_b: &mut [u8]| Ok(()))?;
let bytes: &[u8] = FromPyObject::extract(py_bytes)?;
assert_eq!(bytes, &[0; 10]);
Ok(())
}

#[test]
fn test_bytes_new_with_error() {
use crate::exceptions::PyValueError;
let gil = Python::acquire_gil();
let py = gil.python();
let py_bytes_result = PyBytes::new_with(py, 10, |_b: &mut [u8]| {
Err(PyValueError::py_err("Hello Crustaceans!"))
});
assert!(py_bytes_result.is_err());
assert!(py_bytes_result
.err()
.unwrap()
.is_instance::<PyValueError>(py));
}
}