diff --git a/rclrs/src/context.rs b/rclrs/src/context.rs index c0116dd7b..2e6c9a103 100644 --- a/rclrs/src/context.rs +++ b/rclrs/src/context.rs @@ -1,11 +1,11 @@ +mod builder; + use crate::rcl_bindings::*; -use crate::{RclrsError, ToResult}; +use crate::RclrsError; -use std::ffi::CString; -use std::os::raw::c_char; +use self::builder::*; use std::string::String; use std::sync::Arc; -use std::vec::Vec; use parking_lot::Mutex; @@ -60,49 +60,8 @@ impl Context { /// assert!(Context::new(invalid_remapping).is_err()); /// ``` pub fn new(args: impl IntoIterator) -> Result { - // SAFETY: Getting a zero-initialized value is always safe - let mut rcl_context = unsafe { rcl_get_zero_initialized_context() }; - let cstring_args: Vec = args - .into_iter() - .map(|arg| { - CString::new(arg.as_str()).map_err(|err| RclrsError::StringContainsNul { - err, - s: arg.clone(), - }) - }) - .collect::>()?; - // Vector of pointers into cstring_args - let c_args: Vec<*const c_char> = cstring_args.iter().map(|arg| arg.as_ptr()).collect(); - unsafe { - // SAFETY: No preconditions for this function. - let allocator = rcutils_get_default_allocator(); - // SAFETY: Getting a zero-initialized value is always safe. - let mut rcl_init_options = rcl_get_zero_initialized_init_options(); - // SAFETY: Passing in a zero-initialized value is expected. - // In the case where this returns not ok, there's nothing to clean up. - rcl_init_options_init(&mut rcl_init_options, allocator).ok()?; - // SAFETY: This function does not store the ephemeral init_options and c_args - // pointers. Passing in a zero-initialized rcl_context is expected. - let ret = rcl_init( - c_args.len() as i32, - if c_args.is_empty() { - std::ptr::null() - } else { - c_args.as_ptr() - }, - &rcl_init_options, - &mut rcl_context, - ) - .ok(); - // SAFETY: It's safe to pass in an initialized object. - // Early return will not leak memory, because this is the last fini function. - rcl_init_options_fini(&mut rcl_init_options).ok()?; - // Move the check after the last fini() - ret?; - } - Ok(Self { - rcl_context_mtx: Arc::new(Mutex::new(rcl_context)), - }) + let builder = ContextBuilder::new(args)?; + builder.build() } /// Checks if the context is still valid. diff --git a/rclrs/src/context/builder.rs b/rclrs/src/context/builder.rs new file mode 100644 index 000000000..2f7e80b01 --- /dev/null +++ b/rclrs/src/context/builder.rs @@ -0,0 +1,71 @@ +use crate::error::ToResult; +use crate::rcl_bindings::*; +use crate::{Context, RclrsError}; +use std::ffi::CString; +use std::sync::Arc; + +use parking_lot::Mutex; +use std::os::raw::c_char; + +pub struct ContextBuilder { + cstring_args: Vec, + init_options_mtx: Mutex, +} + +impl ContextBuilder { + /// Build a new ContextBuilder instance + pub fn new(args: impl IntoIterator) -> Result { + Ok(ContextBuilder { + cstring_args: args + .into_iter() + .map(|arg| { + CString::new(arg.as_str()).map_err(|err| RclrsError::StringContainsNul { + err, + s: arg.clone(), + }) + }) + .collect::>()?, + // SAFETY: Getting a zero-initialized value is always safe. + init_options_mtx: unsafe { Mutex::new(rcl_get_zero_initialized_init_options()) }, + }) + } + + /// Function to build the Context instance + pub fn build(&self) -> Result { + let mut rcl_init_options = self.init_options_mtx.lock(); + + let c_args: Vec<*const c_char> = self.cstring_args.iter().map(|arg| arg.as_ptr()).collect(); + unsafe { + // SAFETY: Getting a zero-initialized value is always safe + let mut rcl_context: rcl_context_t = rcl_get_zero_initialized_context(); + // SAFETY: No preconditions for this function. + let allocator: rcutils_allocator_t = rcutils_get_default_allocator(); + + // SAFETY: Passing in a zero-initialized value is expected. + // In the case where this returns not ok, there's nothing to clean up. + rcl_init_options_init(&mut *rcl_init_options, allocator).ok()?; + // SAFETY: This function does not store the ephemeral init_options and c_args + // pointers. Passing in a zero-initialized rcl_context is expected. + + let ret = rcl_init( + c_args.len() as i32, + if c_args.is_empty() { + std::ptr::null() + } else { + c_args.as_ptr() + }, + &*rcl_init_options, + &mut rcl_context, + ) + .ok(); + // SAFETY: It's safe to pass in an initialized object. + // Early return will not leak memory, because this is the last fini function. + rcl_init_options_fini(&mut *rcl_init_options).ok()?; + // Move the check after the last fini() + ret?; + Ok(Context { + rcl_context_mtx: Arc::new(Mutex::new(rcl_context)), + }) + } + } +}