diff --git a/plugins/warp/src/matcher.rs b/plugins/warp/src/matcher.rs
index 81458f8da..6352cd7ae 100644
--- a/plugins/warp/src/matcher.rs
+++ b/plugins/warp/src/matcher.rs
@@ -506,7 +506,7 @@ pub struct PlatformID(u64);
 impl From<&Platform> for PlatformID {
     fn from(value: &Platform) -> Self {
         let mut hasher = DefaultHasher::new();
-        hasher.write(value.name().to_bytes());
+        hasher.write(value.name().as_bytes());
         Self(hasher.finish())
     }
 }
diff --git a/rust/src/architecture.rs b/rust/src/architecture.rs
index 3573f1fb2..82ca0ec53 100644
--- a/rust/src/architecture.rs
+++ b/rust/src/architecture.rs
@@ -27,7 +27,7 @@ use crate::{
     platform::Platform,
     rc::*,
     relocation::CoreRelocationHandler,
-    string::BnStrCompatible,
+    string::AsCStr,
     string::*,
     types::{NameAndType, Type},
     Endianness,
@@ -1397,8 +1397,7 @@ impl CoreArchitecture {
     }
 
     pub fn by_name(name: &str) -> Option<Self> {
-        let handle =
-            unsafe { BNGetArchitectureByName(name.into_bytes_with_nul().as_ptr() as *mut _) };
+        let handle = unsafe { BNGetArchitectureByName(name.as_cstr().as_ptr()) };
         match handle.is_null() {
             false => Some(CoreArchitecture { handle }),
             true => None,
@@ -1945,11 +1944,9 @@ macro_rules! cc_func {
 
 /// Contains helper methods for all types implementing 'Architecture'
 pub trait ArchitectureExt: Architecture {
-    fn register_by_name<S: BnStrCompatible>(&self, name: S) -> Option<Self::Register> {
-        let name = name.into_bytes_with_nul();
-
+    fn register_by_name<S: AsCStr>(&self, name: S) -> Option<Self::Register> {
         match unsafe {
-            BNGetArchitectureRegisterByName(self.as_ref().handle, name.as_ref().as_ptr() as *mut _)
+            BNGetArchitectureRegisterByName(self.as_ref().handle, name.as_cstr().as_ptr())
         } {
             0xffff_ffff => None,
             reg => self.register_from_id(reg.into()),
@@ -2025,7 +2022,7 @@ pub trait ArchitectureExt: Architecture {
 
     fn register_relocation_handler<S, R, F>(&self, name: S, func: F)
     where
-        S: BnStrCompatible,
+        S: AsCStr,
         R: 'static
             + RelocationHandler<Handle = CustomRelocationHandlerHandle<R>>
             + Send
@@ -2048,7 +2045,7 @@ impl<T: Architecture> ArchitectureExt for T {}
 
 pub fn register_architecture<S, A, F>(name: S, func: F) -> &'static A
 where
-    S: BnStrCompatible,
+    S: AsCStr,
     A: 'static + Architecture<Handle = CustomArchitectureHandle<A>> + Send + Sync + Sized,
     F: FnOnce(CustomArchitectureHandle<A>, CoreArchitecture) -> A,
 {
@@ -3134,8 +3131,6 @@ where
         custom_arch.skip_and_return_value(data, addr, val)
     }
 
-    let name = name.into_bytes_with_nul();
-
     let uninit_arch = ArchitectureBuilder {
         arch: MaybeUninit::zeroed(),
         func: Some(func),
@@ -3225,8 +3220,7 @@ where
     };
 
     unsafe {
-        let res =
-            BNRegisterArchitecture(name.as_ref().as_ptr() as *mut _, &mut custom_arch as *mut _);
+        let res = BNRegisterArchitecture(name.as_cstr().as_ptr(), &mut custom_arch as *mut _);
 
         assert!(!res.is_null());
 
diff --git a/rust/src/background_task.rs b/rust/src/background_task.rs
index c818d5b95..71e969501 100644
--- a/rust/src/background_task.rs
+++ b/rust/src/background_task.rs
@@ -43,9 +43,8 @@ impl BackgroundTask {
         Self { handle }
     }
 
-    pub fn new<S: BnStrCompatible>(initial_text: S, can_cancel: bool) -> Ref<Self> {
-        let text = initial_text.into_bytes_with_nul();
-        let handle = unsafe { BNBeginBackgroundTask(text.as_ref().as_ptr() as *mut _, can_cancel) };
+    pub fn new<S: AsCStr>(initial_text: S, can_cancel: bool) -> Ref<Self> {
+        let handle = unsafe { BNBeginBackgroundTask(initial_text.as_cstr().as_ptr(), can_cancel) };
         // We should always be returned a valid task.
         assert!(!handle.is_null());
         unsafe { Ref::new(Self { handle }) }
@@ -75,11 +74,8 @@ impl BackgroundTask {
         unsafe { BnString::from_raw(BNGetBackgroundTaskProgressText(self.handle)) }
     }
 
-    pub fn set_progress_text<S: BnStrCompatible>(&self, text: S) {
-        let progress_text = text.into_bytes_with_nul();
-        unsafe {
-            BNSetBackgroundTaskProgressText(self.handle, progress_text.as_ref().as_ptr() as *mut _)
-        }
+    pub fn set_progress_text<S: AsCStr>(&self, text: S) {
+        unsafe { BNSetBackgroundTaskProgressText(self.handle, text.as_cstr().as_ptr()) }
     }
 
     pub fn running_tasks() -> Array<BackgroundTask> {
diff --git a/rust/src/binary_view.rs b/rust/src/binary_view.rs
index db4c92d74..e90403b39 100644
--- a/rust/src/binary_view.rs
+++ b/rust/src/binary_view.rs
@@ -266,13 +266,8 @@ pub trait BinaryViewExt: BinaryViewBase {
         unsafe { BNGetEndOffset(self.as_ref().handle) }
     }
 
-    fn add_analysis_option(&self, name: impl BnStrCompatible) {
-        unsafe {
-            BNAddAnalysisOption(
-                self.as_ref().handle,
-                name.into_bytes_with_nul().as_ref().as_ptr() as *mut _,
-            )
-        }
+    fn add_analysis_option(&self, name: impl AsCStr) {
+        unsafe { BNAddAnalysisOption(self.as_ref().handle, name.as_cstr().as_ptr()) }
     }
 
     fn has_initial_analysis(&self) -> bool {
@@ -403,13 +398,11 @@ pub trait BinaryViewExt: BinaryViewBase {
         }
     }
 
-    fn symbol_by_raw_name<S: BnStrCompatible>(&self, raw_name: S) -> Option<Ref<Symbol>> {
-        let raw_name = raw_name.into_bytes_with_nul();
-
+    fn symbol_by_raw_name<S: AsCStr>(&self, raw_name: S) -> Option<Ref<Symbol>> {
         unsafe {
             let raw_sym_ptr = BNGetSymbolByRawName(
                 self.as_ref().handle,
-                raw_name.as_ref().as_ptr() as *mut _,
+                raw_name.as_cstr().as_ptr(),
                 std::ptr::null_mut(),
             );
             match raw_sym_ptr.is_null() {
@@ -428,14 +421,12 @@ pub trait BinaryViewExt: BinaryViewBase {
         }
     }
 
-    fn symbols_by_name<S: BnStrCompatible>(&self, name: S) -> Array<Symbol> {
-        let raw_name = name.into_bytes_with_nul();
-
+    fn symbols_by_name<S: AsCStr>(&self, name: S) -> Array<Symbol> {
         unsafe {
             let mut count = 0;
             let handles = BNGetSymbolsByName(
                 self.as_ref().handle,
-                raw_name.as_ref().as_ptr() as *mut _,
+                name.as_cstr().as_ptr(),
                 &mut count,
                 std::ptr::null_mut(),
             );
@@ -589,35 +580,32 @@ pub trait BinaryViewExt: BinaryViewBase {
         }
     }
 
-    fn define_auto_type<T: Into<QualifiedName>, S: BnStrCompatible>(
+    fn define_auto_type<T: Into<QualifiedName>, S: AsCStr>(
         &self,
         name: T,
         source: S,
         type_obj: &Type,
     ) -> QualifiedName {
         let mut raw_name = QualifiedName::into_raw(name.into());
-        let source_str = source.into_bytes_with_nul();
         let name_handle = unsafe {
-            let id_str =
-                BNGenerateAutoTypeId(source_str.as_ref().as_ptr() as *const _, &mut raw_name);
+            let id_str = BNGenerateAutoTypeId(source.as_cstr().as_ptr(), &mut raw_name);
             BNDefineAnalysisType(self.as_ref().handle, id_str, &mut raw_name, type_obj.handle)
         };
         QualifiedName::free_raw(raw_name);
         QualifiedName::from_owned_raw(name_handle)
     }
 
-    fn define_auto_type_with_id<T: Into<QualifiedName>, S: BnStrCompatible>(
+    fn define_auto_type_with_id<T: Into<QualifiedName>, S: AsCStr>(
         &self,
         name: T,
         id: S,
         type_obj: &Type,
     ) -> QualifiedName {
         let mut raw_name = QualifiedName::into_raw(name.into());
-        let id_str = id.into_bytes_with_nul();
         let result_raw_name = unsafe {
             BNDefineAnalysisType(
                 self.as_ref().handle,
-                id_str.as_ref().as_ptr() as *const _,
+                id.as_cstr().as_ptr(),
                 &mut raw_name,
                 type_obj.handle,
             )
@@ -716,11 +704,8 @@ pub trait BinaryViewExt: BinaryViewBase {
         }
     }
 
-    fn undefine_auto_type<S: BnStrCompatible>(&self, id: S) {
-        let id_str = id.into_bytes_with_nul();
-        unsafe {
-            BNUndefineAnalysisType(self.as_ref().handle, id_str.as_ref().as_ptr() as *const _);
-        }
+    fn undefine_auto_type<S: AsCStr>(&self, id: S) {
+        unsafe { BNUndefineAnalysisType(self.as_ref().handle, id.as_cstr().as_ptr()) }
     }
 
     fn undefine_user_type<T: Into<QualifiedName>>(&self, name: T) {
@@ -767,11 +752,9 @@ pub trait BinaryViewExt: BinaryViewBase {
         }
     }
 
-    fn type_by_id<S: BnStrCompatible>(&self, id: S) -> Option<Ref<Type>> {
+    fn type_by_id<S: AsCStr>(&self, id: S) -> Option<Ref<Type>> {
         unsafe {
-            let id_str = id.into_bytes_with_nul();
-            let type_handle =
-                BNGetAnalysisTypeById(self.as_ref().handle, id_str.as_ref().as_ptr() as *mut _);
+            let type_handle = BNGetAnalysisTypeById(self.as_ref().handle, id.as_cstr().as_ptr());
             if type_handle.is_null() {
                 return None;
             }
@@ -779,11 +762,10 @@ pub trait BinaryViewExt: BinaryViewBase {
         }
     }
 
-    fn type_name_by_id<S: BnStrCompatible>(&self, id: S) -> Option<QualifiedName> {
+    fn type_name_by_id<S: AsCStr>(&self, id: S) -> Option<QualifiedName> {
         unsafe {
-            let id_str = id.into_bytes_with_nul();
             let name_handle =
-                BNGetAnalysisTypeNameById(self.as_ref().handle, id_str.as_ref().as_ptr() as *mut _);
+                BNGetAnalysisTypeNameById(self.as_ref().handle, id.as_cstr().as_ptr());
             let name = QualifiedName::from_owned_raw(name_handle);
             // The core will return an empty qualified name if no type name was found.
             match name.items.is_empty() {
@@ -877,27 +859,21 @@ pub trait BinaryViewExt: BinaryViewBase {
         section.create(self.as_ref());
     }
 
-    fn remove_auto_section<S: BnStrCompatible>(&self, name: S) {
-        let raw_name = name.into_bytes_with_nul();
-        let raw_name_ptr = raw_name.as_ref().as_ptr() as *mut _;
+    fn remove_auto_section<S: AsCStr>(&self, name: S) {
         unsafe {
-            BNRemoveAutoSection(self.as_ref().handle, raw_name_ptr);
+            BNRemoveAutoSection(self.as_ref().handle, name.as_cstr().as_ptr());
         }
     }
 
-    fn remove_user_section<S: BnStrCompatible>(&self, name: S) {
-        let raw_name = name.into_bytes_with_nul();
-        let raw_name_ptr = raw_name.as_ref().as_ptr() as *mut _;
+    fn remove_user_section<S: AsCStr>(&self, name: S) {
         unsafe {
-            BNRemoveUserSection(self.as_ref().handle, raw_name_ptr);
+            BNRemoveUserSection(self.as_ref().handle, name.as_cstr().as_ptr());
         }
     }
 
-    fn section_by_name<S: BnStrCompatible>(&self, name: S) -> Option<Ref<Section>> {
+    fn section_by_name<S: AsCStr>(&self, name: S) -> Option<Ref<Section>> {
         unsafe {
-            let raw_name = name.into_bytes_with_nul();
-            let name_ptr = raw_name.as_ref().as_ptr() as *mut _;
-            let raw_section_ptr = BNGetSectionByName(self.as_ref().handle, name_ptr);
+            let raw_section_ptr = BNGetSectionByName(self.as_ref().handle, name.as_cstr().as_ptr());
             match raw_section_ptr.is_null() {
                 false => Some(Section::ref_from_raw(raw_section_ptr)),
                 true => None,
@@ -1109,24 +1085,19 @@ pub trait BinaryViewExt: BinaryViewBase {
         unsafe { BNApplyDebugInfo(self.as_ref().handle, debug_info.handle) }
     }
 
-    fn show_graph_report<S: BnStrCompatible>(&self, raw_name: S, graph: &FlowGraph) {
-        let raw_name = raw_name.into_bytes_with_nul();
+    fn show_graph_report<S: AsCStr>(&self, raw_name: S, graph: &FlowGraph) {
         unsafe {
             BNShowGraphReport(
                 self.as_ref().handle,
-                raw_name.as_ref().as_ptr() as *mut _,
+                raw_name.as_cstr().as_ptr(),
                 graph.handle,
             );
         }
     }
 
-    fn load_settings<S: BnStrCompatible>(&self, view_type_name: S) -> Result<Ref<Settings>> {
-        let view_type_name = view_type_name.into_bytes_with_nul();
+    fn load_settings<S: AsCStr>(&self, view_type_name: S) -> Result<Ref<Settings>> {
         let settings_handle = unsafe {
-            BNBinaryViewGetLoadSettings(
-                self.as_ref().handle,
-                view_type_name.as_ref().as_ptr() as *mut _,
-            )
+            BNBinaryViewGetLoadSettings(self.as_ref().handle, view_type_name.as_cstr().as_ptr())
         };
 
         if settings_handle.is_null() {
@@ -1136,13 +1107,11 @@ pub trait BinaryViewExt: BinaryViewBase {
         }
     }
 
-    fn set_load_settings<S: BnStrCompatible>(&self, view_type_name: S, settings: &Settings) {
-        let view_type_name = view_type_name.into_bytes_with_nul();
-
+    fn set_load_settings<S: AsCStr>(&self, view_type_name: S, settings: &Settings) {
         unsafe {
             BNBinaryViewSetLoadSettings(
                 self.as_ref().handle,
-                view_type_name.as_ref().as_ptr() as *mut _,
+                view_type_name.as_cstr().as_ptr(),
                 settings.handle,
             )
         };
@@ -1153,11 +1122,7 @@ pub trait BinaryViewExt: BinaryViewBase {
     /// # Arguments
     /// * `name` - the name for the tag
     /// * `icon` - the icon (recommended 1 emoji or 2 chars) for the tag
-    fn create_tag_type<N: BnStrCompatible, I: BnStrCompatible>(
-        &self,
-        name: N,
-        icon: I,
-    ) -> Ref<TagType> {
+    fn create_tag_type<N: AsCStr, I: AsCStr>(&self, name: N, icon: I) -> Ref<TagType> {
         let tag_type = TagType::create(self.as_ref(), name, icon);
         unsafe {
             BNAddTagType(self.as_ref().handle, tag_type.handle);
@@ -1171,10 +1136,9 @@ pub trait BinaryViewExt: BinaryViewBase {
     }
 
     /// Get a tag type by its name.
-    fn tag_type_by_name<S: BnStrCompatible>(&self, name: S) -> Option<Ref<TagType>> {
-        let name = name.into_bytes_with_nul();
+    fn tag_type_by_name<S: AsCStr>(&self, name: S) -> Option<Ref<TagType>> {
         unsafe {
-            let handle = BNGetTagType(self.as_ref().handle, name.as_ref().as_ptr() as *mut _);
+            let handle = BNGetTagType(self.as_ref().handle, name.as_cstr().as_ptr());
             if handle.is_null() {
                 return None;
             }
@@ -1185,10 +1149,9 @@ pub trait BinaryViewExt: BinaryViewBase {
     /// Get a tag by its id.
     ///
     /// Note this does not tell you anything about where it is used.
-    fn tag_by_id<S: BnStrCompatible>(&self, id: S) -> Option<Ref<Tag>> {
-        let id = id.into_bytes_with_nul();
+    fn tag_by_id<S: AsCStr>(&self, id: S) -> Option<Ref<Tag>> {
         unsafe {
-            let handle = BNGetTag(self.as_ref().handle, id.as_ref().as_ptr() as *mut _);
+            let handle = BNGetTag(self.as_ref().handle, id.as_cstr().as_ptr());
             if handle.is_null() {
                 return None;
             }
@@ -1199,7 +1162,7 @@ pub trait BinaryViewExt: BinaryViewBase {
     /// Creates and adds a tag to an address
     ///
     /// User tag creations will be added to the undo buffer
-    fn add_tag<S: BnStrCompatible>(&self, addr: u64, t: &TagType, data: S, user: bool) {
+    fn add_tag<S: AsCStr>(&self, addr: u64, t: &TagType, data: S, user: bool) {
         let tag = Tag::new(t, data);
 
         unsafe { BNAddTag(self.as_ref().handle, tag.handle, user) }
@@ -1236,14 +1199,9 @@ pub trait BinaryViewExt: BinaryViewBase {
     ///
     /// NOTE: This is different from setting a comment at the function-level. To set a comment in a
     /// function use [`Function::set_comment_at`]
-    fn set_comment_at(&self, addr: u64, comment: impl BnStrCompatible) {
-        let comment_raw = comment.into_bytes_with_nul();
+    fn set_comment_at(&self, addr: u64, comment: impl AsCStr) {
         unsafe {
-            BNSetGlobalCommentForAddress(
-                self.as_ref().handle,
-                addr,
-                comment_raw.as_ref().as_ptr() as *const c_char,
-            )
+            BNSetGlobalCommentForAddress(self.as_ref().handle, addr, comment.as_cstr().as_ptr())
         }
     }
 
@@ -1295,13 +1253,9 @@ pub trait BinaryViewExt: BinaryViewBase {
         result
     }
 
-    fn query_metadata<S: BnStrCompatible>(&self, key: S) -> Option<Ref<Metadata>> {
-        let value: *mut BNMetadata = unsafe {
-            BNBinaryViewQueryMetadata(
-                self.as_ref().handle,
-                key.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
-            )
-        };
+    fn query_metadata<S: AsCStr>(&self, key: S) -> Option<Ref<Metadata>> {
+        let value: *mut BNMetadata =
+            unsafe { BNBinaryViewQueryMetadata(self.as_ref().handle, key.as_cstr().as_ptr()) };
         if value.is_null() {
             None
         } else {
@@ -1309,7 +1263,7 @@ pub trait BinaryViewExt: BinaryViewBase {
         }
     }
 
-    fn get_metadata<T, S: BnStrCompatible>(&self, key: S) -> Option<Result<T>>
+    fn get_metadata<T, S: AsCStr>(&self, key: S) -> Option<Result<T>>
     where
         T: for<'a> TryFrom<&'a Metadata>,
     {
@@ -1317,7 +1271,7 @@ pub trait BinaryViewExt: BinaryViewBase {
             .map(|md| T::try_from(md.as_ref()).map_err(|_| ()))
     }
 
-    fn store_metadata<V, S: BnStrCompatible>(&self, key: S, value: V, is_auto: bool)
+    fn store_metadata<V, S: AsCStr>(&self, key: S, value: V, is_auto: bool)
     where
         V: Into<Ref<Metadata>>,
     {
@@ -1325,20 +1279,15 @@ pub trait BinaryViewExt: BinaryViewBase {
         unsafe {
             BNBinaryViewStoreMetadata(
                 self.as_ref().handle,
-                key.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
+                key.as_cstr().as_ptr(),
                 md.as_ref().handle,
                 is_auto,
             )
         };
     }
 
-    fn remove_metadata<S: BnStrCompatible>(&self, key: S) {
-        unsafe {
-            BNBinaryViewRemoveMetadata(
-                self.as_ref().handle,
-                key.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
-            )
-        };
+    fn remove_metadata<S: AsCStr>(&self, key: S) {
+        unsafe { BNBinaryViewRemoveMetadata(self.as_ref().handle, key.as_cstr().as_ptr()) };
     }
 
     /// Retrieves a list of [CodeReference]s pointing to a given address.
@@ -1462,14 +1411,8 @@ pub trait BinaryViewExt: BinaryViewBase {
             .collect()
     }
 
-    fn component_by_guid<S: BnStrCompatible>(&self, guid: S) -> Option<Ref<Component>> {
-        let name = guid.into_bytes_with_nul();
-        let result = unsafe {
-            BNGetComponentByGuid(
-                self.as_ref().handle,
-                name.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    fn component_by_guid<S: AsCStr>(&self, guid: S) -> Option<Ref<Component>> {
+        let result = unsafe { BNGetComponentByGuid(self.as_ref().handle, guid.as_cstr().as_ptr()) };
         NonNull::new(result).map(|h| unsafe { Component::ref_from_raw(h) })
     }
 
@@ -1478,14 +1421,8 @@ pub trait BinaryViewExt: BinaryViewBase {
         NonNull::new(result).map(|h| unsafe { Component::ref_from_raw(h) })
     }
 
-    fn component_by_path<P: BnStrCompatible>(&self, path: P) -> Option<Ref<Component>> {
-        let path = path.into_bytes_with_nul();
-        let result = unsafe {
-            BNGetComponentByPath(
-                self.as_ref().handle,
-                path.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    fn component_by_path<P: AsCStr>(&self, path: P) -> Option<Ref<Component>> {
+        let result = unsafe { BNGetComponentByPath(self.as_ref().handle, path.as_cstr().as_ptr()) };
         NonNull::new(result).map(|h| unsafe { Component::ref_from_raw(h) })
     }
 
@@ -1494,8 +1431,7 @@ pub trait BinaryViewExt: BinaryViewBase {
     }
 
     fn remove_component_by_guid<P: IntoComponentGuid>(&self, guid: P) -> bool {
-        let path = guid.component_guid();
-        unsafe { BNRemoveComponentByGuid(self.as_ref().handle, path.as_ptr()) }
+        unsafe { BNRemoveComponentByGuid(self.as_ref().handle, guid.component_guid().as_ptr()) }
     }
 
     fn data_variable_parent_components(&self, data_variable: &DataVariable) -> Array<Component> {
@@ -1516,39 +1452,28 @@ pub trait BinaryViewExt: BinaryViewBase {
         unsafe { Array::new(result, count, ()) }
     }
 
-    fn external_library<S: BnStrCompatible>(&self, name: S) -> Option<Ref<ExternalLibrary>> {
-        let name_ptr = name.into_bytes_with_nul();
+    fn external_library<S: AsCStr>(&self, name: S) -> Option<Ref<ExternalLibrary>> {
         let result = unsafe {
-            BNBinaryViewGetExternalLibrary(
-                self.as_ref().handle,
-                name_ptr.as_ref().as_ptr() as *const c_char,
-            )
+            BNBinaryViewGetExternalLibrary(self.as_ref().handle, name.as_cstr().as_ptr())
         };
         let result_ptr = NonNull::new(result)?;
         Some(unsafe { ExternalLibrary::ref_from_raw(result_ptr) })
     }
 
-    fn remove_external_library<S: BnStrCompatible>(&self, name: S) {
-        let name_ptr = name.into_bytes_with_nul();
-        unsafe {
-            BNBinaryViewRemoveExternalLibrary(
-                self.as_ref().handle,
-                name_ptr.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    fn remove_external_library<S: AsCStr>(&self, name: S) {
+        unsafe { BNBinaryViewRemoveExternalLibrary(self.as_ref().handle, name.as_cstr().as_ptr()) };
     }
 
-    fn add_external_library<S: BnStrCompatible>(
+    fn add_external_library<S: AsCStr>(
         &self,
         name: S,
         backing_file: Option<&ProjectFile>,
         auto: bool,
     ) -> Option<Ref<ExternalLibrary>> {
-        let name_ptr = name.into_bytes_with_nul();
         let result = unsafe {
             BNBinaryViewAddExternalLibrary(
                 self.as_ref().handle,
-                name_ptr.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
                 backing_file
                     .map(|b| b.handle.as_ptr())
                     .unwrap_or(std::ptr::null_mut()),
@@ -1580,7 +1505,7 @@ pub trait BinaryViewExt: BinaryViewBase {
     }
 
     // TODO: This is awful, rewrite this.
-    fn add_external_location<S: BnStrCompatible>(
+    fn add_external_location<S: AsCStr>(
         &self,
         symbol: &Symbol,
         library: &ExternalLibrary,
@@ -1588,7 +1513,6 @@ pub trait BinaryViewExt: BinaryViewBase {
         target_address: Option<u64>,
         target_is_auto: bool,
     ) -> Option<Ref<ExternalLocation>> {
-        let target_symbol_name = target_symbol_name.into_bytes_with_nul();
         let target_address_ptr = target_address
             .map(|a| a as *mut u64)
             .unwrap_or(std::ptr::null_mut());
@@ -1597,7 +1521,7 @@ pub trait BinaryViewExt: BinaryViewBase {
                 self.as_ref().handle,
                 symbol.handle,
                 library.handle.as_ptr(),
-                target_symbol_name.as_ref().as_ptr() as *const c_char,
+                target_symbol_name.as_cstr().as_ptr(),
                 target_address_ptr,
                 target_is_auto,
             )
@@ -1638,14 +1562,9 @@ pub trait BinaryViewExt: BinaryViewBase {
         unsafe { BNAddBinaryViewTypeLibrary(self.as_ref().handle, library.as_raw()) }
     }
 
-    fn type_library_by_name<S: BnStrCompatible>(&self, name: S) -> Option<TypeLibrary> {
-        let name = name.into_bytes_with_nul();
-        let result = unsafe {
-            BNGetBinaryViewTypeLibrary(
-                self.as_ref().handle,
-                name.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    fn type_library_by_name<S: AsCStr>(&self, name: S) -> Option<TypeLibrary> {
+        let result =
+            unsafe { BNGetBinaryViewTypeLibrary(self.as_ref().handle, name.as_cstr().as_ptr()) };
         NonNull::new(result).map(|h| unsafe { TypeLibrary::from_raw(h) })
     }
 
@@ -1736,13 +1655,9 @@ pub trait BinaryViewExt: BinaryViewBase {
     ///     contain a metadata key called "type_guids" which is a map
     ///     Dict[string_guid, string_type_name] or
     ///     Dict[string_guid, Tuple[string_type_name, type_library_name]]
-    fn import_type_by_guid<S: BnStrCompatible>(&self, guid: S) -> Option<Ref<Type>> {
-        let guid = guid.into_bytes_with_nul();
+    fn import_type_by_guid<S: AsCStr>(&self, guid: S) -> Option<Ref<Type>> {
         let result = unsafe {
-            BNBinaryViewImportTypeLibraryTypeByGuid(
-                self.as_ref().handle,
-                guid.as_ref().as_ptr() as *const c_char,
-            )
+            BNBinaryViewImportTypeLibraryTypeByGuid(self.as_ref().handle, guid.as_cstr().as_ptr())
         };
         (!result.is_null()).then(|| unsafe { Type::ref_from_raw(result) })
     }
@@ -1884,9 +1799,9 @@ impl BinaryView {
     }
 
     pub fn from_path(meta: &mut FileMetadata, file_path: impl AsRef<Path>) -> Result<Ref<Self>> {
-        let file = file_path.as_ref().into_bytes_with_nul();
-        let handle =
-            unsafe { BNCreateBinaryDataViewFromFilename(meta.handle, file.as_ptr() as *mut _) };
+        let handle = unsafe {
+            BNCreateBinaryDataViewFromFilename(meta.handle, file_path.as_ref().as_cstr().as_ptr())
+        };
 
         if handle.is_null() {
             return Err(());
@@ -1926,8 +1841,7 @@ impl BinaryView {
     /// To avoid the above issue use [`crate::main_thread::execute_on_main_thread_and_wait`] to verify there
     /// are no queued up main thread actions.
     pub fn save_to_path(&self, file_path: impl AsRef<Path>) -> bool {
-        let file = file_path.as_ref().into_bytes_with_nul();
-        unsafe { BNSaveToFilename(self.handle, file.as_ptr() as *mut _) }
+        unsafe { BNSaveToFilename(self.handle, file_path.as_ref().as_cstr().as_ptr()) }
     }
 
     /// Save the original binary file to the provided [`FileAccessor`] along with any modifications.
diff --git a/rust/src/binary_view/memory_map.rs b/rust/src/binary_view/memory_map.rs
index 3e248c545..9d8d6c761 100644
--- a/rust/src/binary_view/memory_map.rs
+++ b/rust/src/binary_view/memory_map.rs
@@ -3,9 +3,8 @@ use crate::data_buffer::DataBuffer;
 use crate::file_accessor::FileAccessor;
 use crate::rc::Ref;
 use crate::segment::SegmentFlags;
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use binaryninjacore_sys::*;
-use std::ffi::c_char;
 
 #[derive(PartialEq, Eq, Hash)]
 pub struct MemoryMap {
@@ -43,16 +42,15 @@ impl MemoryMap {
 
     pub fn add_binary_memory_region(
         &mut self,
-        name: impl BnStrCompatible,
+        name: impl AsCStr,
         start: u64,
         view: &BinaryView,
         segment_flags: Option<SegmentFlags>,
     ) -> bool {
-        let name_raw = name.into_bytes_with_nul();
         unsafe {
             BNAddBinaryMemoryRegion(
                 self.view.handle,
-                name_raw.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
                 start,
                 view.handle,
                 segment_flags.unwrap_or_default().into_raw(),
@@ -62,16 +60,15 @@ impl MemoryMap {
 
     pub fn add_data_memory_region(
         &mut self,
-        name: impl BnStrCompatible,
+        name: impl AsCStr,
         start: u64,
         data: &DataBuffer,
         segment_flags: Option<SegmentFlags>,
     ) -> bool {
-        let name_raw = name.into_bytes_with_nul();
         unsafe {
             BNAddDataMemoryRegion(
                 self.view.handle,
-                name_raw.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
                 start,
                 data.as_raw(),
                 segment_flags.unwrap_or_default().into_raw(),
@@ -81,16 +78,15 @@ impl MemoryMap {
 
     pub fn add_remote_memory_region(
         &mut self,
-        name: impl BnStrCompatible,
+        name: impl AsCStr,
         start: u64,
         accessor: &mut FileAccessor,
         segment_flags: Option<SegmentFlags>,
     ) -> bool {
-        let name_raw = name.into_bytes_with_nul();
         unsafe {
             BNAddRemoteMemoryRegion(
                 self.view.handle,
-                name_raw.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
                 start,
                 &mut accessor.api_object,
                 segment_flags.unwrap_or_default().into_raw(),
@@ -98,14 +94,8 @@ impl MemoryMap {
         }
     }
 
-    pub fn remove_memory_region(&mut self, name: impl BnStrCompatible) -> bool {
-        let name_raw = name.into_bytes_with_nul();
-        unsafe {
-            BNRemoveMemoryRegion(
-                self.view.handle,
-                name_raw.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn remove_memory_region(&mut self, name: impl AsCStr) -> bool {
+        unsafe { BNRemoveMemoryRegion(self.view.handle, name.as_cstr().as_ptr()) }
     }
 
     pub fn active_memory_region_at(&self, addr: u64) -> BnString {
@@ -115,98 +105,41 @@ impl MemoryMap {
         }
     }
 
-    pub fn memory_region_flags(&self, name: impl BnStrCompatible) -> SegmentFlags {
-        let name_raw = name.into_bytes_with_nul();
-        let flags_raw = unsafe {
-            BNGetMemoryRegionFlags(
-                self.view.handle,
-                name_raw.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    pub fn memory_region_flags(&self, name: impl AsCStr) -> SegmentFlags {
+        let flags_raw =
+            unsafe { BNGetMemoryRegionFlags(self.view.handle, name.as_cstr().as_ptr()) };
         SegmentFlags::from_raw(flags_raw)
     }
 
-    pub fn set_memory_region_flags(
-        &mut self,
-        name: impl BnStrCompatible,
-        flags: SegmentFlags,
-    ) -> bool {
-        let name_raw = name.into_bytes_with_nul();
+    pub fn set_memory_region_flags(&mut self, name: impl AsCStr, flags: SegmentFlags) -> bool {
         unsafe {
-            BNSetMemoryRegionFlags(
-                self.view.handle,
-                name_raw.as_ref().as_ptr() as *const c_char,
-                flags.into_raw(),
-            )
+            BNSetMemoryRegionFlags(self.view.handle, name.as_cstr().as_ptr(), flags.into_raw())
         }
     }
 
-    pub fn is_memory_region_enabled(&self, name: impl BnStrCompatible) -> bool {
-        let name_raw = name.into_bytes_with_nul();
-        unsafe {
-            BNIsMemoryRegionEnabled(
-                self.view.handle,
-                name_raw.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn is_memory_region_enabled(&self, name: impl AsCStr) -> bool {
+        unsafe { BNIsMemoryRegionEnabled(self.view.handle, name.as_cstr().as_ptr()) }
     }
 
-    pub fn set_memory_region_enabled(&mut self, name: impl BnStrCompatible, enabled: bool) -> bool {
-        let name_raw = name.into_bytes_with_nul();
-        unsafe {
-            BNSetMemoryRegionEnabled(
-                self.view.handle,
-                name_raw.as_ref().as_ptr() as *const c_char,
-                enabled,
-            )
-        }
+    pub fn set_memory_region_enabled(&mut self, name: impl AsCStr, enabled: bool) -> bool {
+        unsafe { BNSetMemoryRegionEnabled(self.view.handle, name.as_cstr().as_ptr(), enabled) }
     }
 
     // TODO: Should we just call this is_memory_region_relocatable?
-    pub fn is_memory_region_rebaseable(&self, name: impl BnStrCompatible) -> bool {
-        let name_raw = name.into_bytes_with_nul();
-        unsafe {
-            BNIsMemoryRegionRebaseable(
-                self.view.handle,
-                name_raw.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn is_memory_region_rebaseable(&self, name: impl AsCStr) -> bool {
+        unsafe { BNIsMemoryRegionRebaseable(self.view.handle, name.as_cstr().as_ptr()) }
     }
 
-    pub fn set_memory_region_rebaseable(
-        &mut self,
-        name: impl BnStrCompatible,
-        enabled: bool,
-    ) -> bool {
-        let name_raw = name.into_bytes_with_nul();
-        unsafe {
-            BNSetMemoryRegionRebaseable(
-                self.view.handle,
-                name_raw.as_ref().as_ptr() as *const c_char,
-                enabled,
-            )
-        }
+    pub fn set_memory_region_rebaseable(&mut self, name: impl AsCStr, enabled: bool) -> bool {
+        unsafe { BNSetMemoryRegionRebaseable(self.view.handle, name.as_cstr().as_ptr(), enabled) }
     }
 
-    pub fn memory_region_fill(&self, name: impl BnStrCompatible) -> u8 {
-        let name_raw = name.into_bytes_with_nul();
-        unsafe {
-            BNGetMemoryRegionFill(
-                self.view.handle,
-                name_raw.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn memory_region_fill(&self, name: impl AsCStr) -> u8 {
+        unsafe { BNGetMemoryRegionFill(self.view.handle, name.as_cstr().as_ptr()) }
     }
 
-    pub fn set_memory_region_fill(&mut self, name: impl BnStrCompatible, fill: u8) -> bool {
-        let name_raw = name.into_bytes_with_nul();
-        unsafe {
-            BNSetMemoryRegionFill(
-                self.view.handle,
-                name_raw.as_ref().as_ptr() as *const c_char,
-                fill,
-            )
-        }
+    pub fn set_memory_region_fill(&mut self, name: impl AsCStr, fill: u8) -> bool {
+        unsafe { BNSetMemoryRegionFill(self.view.handle, name.as_cstr().as_ptr(), fill) }
     }
 
     pub fn reset(&mut self) {
diff --git a/rust/src/calling_convention.rs b/rust/src/calling_convention.rs
index 2fb282e0f..07ab7eefd 100644
--- a/rust/src/calling_convention.rs
+++ b/rust/src/calling_convention.rs
@@ -58,7 +58,7 @@ pub trait CallingConvention: Sync {
 pub fn register_calling_convention<A, N, C>(arch: &A, name: N, cc: C) -> Ref<CoreCallingConvention>
 where
     A: Architecture,
-    N: BnStrCompatible,
+    N: AsCStr,
     C: 'static + CallingConvention,
 {
     struct CustomCallingConventionContext<C>
@@ -377,7 +377,6 @@ where
         )
     }
 
-    let name = name.into_bytes_with_nul();
     let raw = Box::into_raw(Box::new(CustomCallingConventionContext {
         raw_handle: std::ptr::null_mut(),
         cc,
@@ -413,8 +412,8 @@ where
     };
 
     unsafe {
-        let cc_name = name.as_ref().as_ptr() as *mut _;
-        let result = BNCreateCallingConvention(arch.as_ref().handle, cc_name, &mut cc);
+        let result =
+            BNCreateCallingConvention(arch.as_ref().handle, name.as_cstr().as_ptr(), &mut cc);
 
         assert!(!result.is_null());
 
diff --git a/rust/src/collaboration.rs b/rust/src/collaboration.rs
index c90677622..2f185fbfc 100644
--- a/rust/src/collaboration.rs
+++ b/rust/src/collaboration.rs
@@ -22,7 +22,6 @@ pub use permission::*;
 pub use project::*;
 pub use remote::*;
 pub use snapshot::*;
-use std::ffi::c_char;
 use std::ptr::NonNull;
 pub use sync::*;
 pub use user::*;
@@ -30,7 +29,7 @@ pub use user::*;
 use binaryninjacore_sys::*;
 
 use crate::rc::{Array, Ref};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 // TODO: Should we pull metadata and information required to call a function? Or should we add documentation
 // TODO: on what functions need to have been called prior? I feel like we should make the user have to pull
@@ -73,24 +72,20 @@ pub fn known_remotes() -> Array<Remote> {
 }
 
 /// Get Remote by unique `id`
-pub fn get_remote_by_id<S: BnStrCompatible>(id: S) -> Option<Ref<Remote>> {
-    let id = id.into_bytes_with_nul();
-    let value = unsafe { BNCollaborationGetRemoteById(id.as_ref().as_ptr() as *const c_char) };
+pub fn get_remote_by_id<S: AsCStr>(id: S) -> Option<Ref<Remote>> {
+    let value = unsafe { BNCollaborationGetRemoteById(id.as_cstr().as_ptr()) };
     NonNull::new(value).map(|h| unsafe { Remote::ref_from_raw(h) })
 }
 
 /// Get Remote by `address`
-pub fn get_remote_by_address<S: BnStrCompatible>(address: S) -> Option<Ref<Remote>> {
-    let address = address.into_bytes_with_nul();
-    let value =
-        unsafe { BNCollaborationGetRemoteByAddress(address.as_ref().as_ptr() as *const c_char) };
+pub fn get_remote_by_address<S: AsCStr>(address: S) -> Option<Ref<Remote>> {
+    let value = unsafe { BNCollaborationGetRemoteByAddress(address.as_cstr().as_ptr()) };
     NonNull::new(value).map(|h| unsafe { Remote::ref_from_raw(h) })
 }
 
 /// Get Remote by `name`
-pub fn get_remote_by_name<S: BnStrCompatible>(name: S) -> Option<Ref<Remote>> {
-    let name = name.into_bytes_with_nul();
-    let value = unsafe { BNCollaborationGetRemoteByName(name.as_ref().as_ptr() as *const c_char) };
+pub fn get_remote_by_name<S: AsCStr>(name: S) -> Option<Ref<Remote>> {
+    let value = unsafe { BNCollaborationGetRemoteByName(name.as_cstr().as_ptr()) };
     NonNull::new(value).map(|h| unsafe { Remote::ref_from_raw(h) })
 }
 
@@ -106,58 +101,44 @@ pub fn save_remotes() {
 
 pub fn store_data_in_keychain<K, I, DK, DV>(key: K, data: I) -> bool
 where
-    K: BnStrCompatible,
+    K: AsCStr,
     I: IntoIterator<Item = (DK, DV)>,
-    DK: BnStrCompatible,
-    DV: BnStrCompatible,
+    DK: AsCStr,
+    DV: AsCStr,
 {
-    let key = key.into_bytes_with_nul();
-    let (data_keys, data_values): (Vec<DK::Result>, Vec<DV::Result>) = data
-        .into_iter()
-        .map(|(k, v)| (k.into_bytes_with_nul(), v.into_bytes_with_nul()))
-        .unzip();
-    let data_keys_ptr: Box<[*const c_char]> = data_keys
-        .iter()
-        .map(|k| k.as_ref().as_ptr() as *const c_char)
-        .collect();
-    let data_values_ptr: Box<[*const c_char]> = data_values
-        .iter()
-        .map(|v| v.as_ref().as_ptr() as *const c_char)
-        .collect();
+    let (keys, values): (Vec<_>, Vec<_>) = data.into_iter().unzip();
+
+    let data_keys = keys.iter().map(|k| k.as_cstr()).collect::<Vec<_>>();
+    let mut data_keys_ptrs = data_keys.iter().map(|k| k.as_ptr()).collect::<Vec<_>>();
+
+    let data_values = values.iter().map(|v| v.as_cstr()).collect::<Vec<_>>();
+    let mut data_values_ptrs = data_values.iter().map(|v| v.as_ptr()).collect::<Vec<_>>();
+
     unsafe {
         BNCollaborationStoreDataInKeychain(
-            key.as_ref().as_ptr() as *const c_char,
-            data_keys_ptr.as_ptr() as *mut _,
-            data_values_ptr.as_ptr() as *mut _,
+            key.as_cstr().as_ptr(),
+            data_keys_ptrs.as_mut_ptr(),
+            data_values_ptrs.as_mut_ptr(),
             data_keys.len(),
         )
     }
 }
 
-pub fn has_data_in_keychain<K: BnStrCompatible>(key: K) -> bool {
-    let key = key.into_bytes_with_nul();
-    unsafe { BNCollaborationHasDataInKeychain(key.as_ref().as_ptr() as *const c_char) }
+pub fn has_data_in_keychain<K: AsCStr>(key: K) -> bool {
+    unsafe { BNCollaborationHasDataInKeychain(key.as_cstr().as_ptr()) }
 }
 
-pub fn get_data_from_keychain<K: BnStrCompatible>(
-    key: K,
-) -> Option<(Array<BnString>, Array<BnString>)> {
-    let key = key.into_bytes_with_nul();
+pub fn get_data_from_keychain<K: AsCStr>(key: K) -> Option<(Array<BnString>, Array<BnString>)> {
     let mut keys = std::ptr::null_mut();
     let mut values = std::ptr::null_mut();
     let count = unsafe {
-        BNCollaborationGetDataFromKeychain(
-            key.as_ref().as_ptr() as *const c_char,
-            &mut keys,
-            &mut values,
-        )
+        BNCollaborationGetDataFromKeychain(key.as_cstr().as_ptr(), &mut keys, &mut values)
     };
     let keys = (!keys.is_null()).then(|| unsafe { Array::new(keys, count, ()) });
     let values = (!values.is_null()).then(|| unsafe { Array::new(values, count, ()) });
     keys.zip(values)
 }
 
-pub fn delete_data_from_keychain<K: BnStrCompatible>(key: K) -> bool {
-    let key = key.into_bytes_with_nul();
-    unsafe { BNCollaborationDeleteDataFromKeychain(key.as_ref().as_ptr() as *const c_char) }
+pub fn delete_data_from_keychain<K: AsCStr>(key: K) -> bool {
+    unsafe { BNCollaborationDeleteDataFromKeychain(key.as_cstr().as_ptr()) }
 }
diff --git a/rust/src/collaboration/changeset.rs b/rust/src/collaboration/changeset.rs
index 9d7cdb7c3..7e522a0ea 100644
--- a/rust/src/collaboration/changeset.rs
+++ b/rust/src/collaboration/changeset.rs
@@ -1,5 +1,4 @@
 use binaryninjacore_sys::*;
-use std::ffi::c_char;
 use std::ptr::NonNull;
 
 use super::{RemoteFile, RemoteUser};
@@ -7,7 +6,7 @@ use super::{RemoteFile, RemoteUser};
 use crate::database::snapshot::SnapshotId;
 use crate::database::Database;
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 /// A collection of snapshots in a local database
 #[repr(transparent)]
@@ -66,14 +65,8 @@ impl Changeset {
     }
 
     /// Set the name of the changeset, e.g. in a name changeset function.
-    pub fn set_name<S: BnStrCompatible>(&self, value: S) -> bool {
-        let value = value.into_bytes_with_nul();
-        unsafe {
-            BNCollaborationChangesetSetName(
-                self.handle.as_ptr(),
-                value.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn set_name<S: AsCStr>(&self, value: S) -> bool {
+        unsafe { BNCollaborationChangesetSetName(self.handle.as_ptr(), value.as_cstr().as_ptr()) }
     }
 }
 
diff --git a/rust/src/collaboration/file.rs b/rust/src/collaboration/file.rs
index 2651d3c77..a58152248 100644
--- a/rust/src/collaboration/file.rs
+++ b/rust/src/collaboration/file.rs
@@ -1,4 +1,4 @@
-use std::ffi::{c_char, c_void};
+use std::ffi::c_void;
 use std::fmt::{Debug, Formatter};
 use std::ptr::NonNull;
 use std::time::SystemTime;
@@ -16,7 +16,7 @@ use crate::file_metadata::FileMetadata;
 use crate::progress::{NoProgressCallback, ProgressCallback, SplitProgressBuilder};
 use crate::project::file::ProjectFile;
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 pub type RemoteFileType = BNRemoteFileType;
 
@@ -94,14 +94,9 @@ impl RemoteFile {
         success.then_some(()).ok_or(())
     }
 
-    pub fn set_metadata<S: BnStrCompatible>(&self, folder: S) -> Result<(), ()> {
-        let folder_raw = folder.into_bytes_with_nul();
-        let success = unsafe {
-            BNRemoteFileSetMetadata(
-                self.handle.as_ptr(),
-                folder_raw.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    pub fn set_metadata<S: AsCStr>(&self, folder: S) -> Result<(), ()> {
+        let success =
+            unsafe { BNRemoteFileSetMetadata(self.handle.as_ptr(), folder.as_cstr().as_ptr()) };
         success.then_some(()).ok_or(())
     }
 
@@ -190,14 +185,8 @@ impl RemoteFile {
     }
 
     /// Set the description of the file. You will need to push the file to update the remote version.
-    pub fn set_name<S: BnStrCompatible>(&self, name: S) -> Result<(), ()> {
-        let name = name.into_bytes_with_nul();
-        let success = unsafe {
-            BNRemoteFileSetName(
-                self.handle.as_ptr(),
-                name.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    pub fn set_name<S: AsCStr>(&self, name: S) -> Result<(), ()> {
+        let success = unsafe { BNRemoteFileSetName(self.handle.as_ptr(), name.as_cstr().as_ptr()) };
         success.then_some(()).ok_or(())
     }
 
@@ -209,13 +198,9 @@ impl RemoteFile {
     }
 
     /// Set the description of the file. You will need to push the file to update the remote version.
-    pub fn set_description<S: BnStrCompatible>(&self, description: S) -> Result<(), ()> {
-        let description = description.into_bytes_with_nul();
+    pub fn set_description<S: AsCStr>(&self, description: S) -> Result<(), ()> {
         let success = unsafe {
-            BNRemoteFileSetDescription(
-                self.handle.as_ptr(),
-                description.as_ref().as_ptr() as *const c_char,
-            )
+            BNRemoteFileSetDescription(self.handle.as_ptr(), description.as_cstr().as_ptr())
         };
         success.then_some(()).ok_or(())
     }
@@ -263,18 +248,13 @@ impl RemoteFile {
     /// Get a specific Snapshot in the File by its id
     ///
     /// NOTE: If snapshots have not been pulled, they will be pulled upon calling this.
-    pub fn snapshot_by_id<S: BnStrCompatible>(
-        &self,
-        id: S,
-    ) -> Result<Option<Ref<RemoteSnapshot>>, ()> {
+    pub fn snapshot_by_id<S: AsCStr>(&self, id: S) -> Result<Option<Ref<RemoteSnapshot>>, ()> {
         // TODO: This sync should be removed?
         if !self.has_pulled_snapshots() {
             self.pull_snapshots()?;
         }
-        let id = id.into_bytes_with_nul();
-        let result = unsafe {
-            BNRemoteFileGetSnapshotById(self.handle.as_ptr(), id.as_ref().as_ptr() as *const c_char)
-        };
+        let result =
+            unsafe { BNRemoteFileGetSnapshotById(self.handle.as_ptr(), id.as_cstr().as_ptr()) };
         Ok(NonNull::new(result).map(|handle| unsafe { RemoteSnapshot::ref_from_raw(handle) }))
     }
 
@@ -314,9 +294,9 @@ impl RemoteFile {
         parent_ids: I,
     ) -> Result<Ref<RemoteSnapshot>, ()>
     where
-        S: BnStrCompatible,
+        S: AsCStr,
         I: IntoIterator,
-        I::Item: BnStrCompatible,
+        I::Item: AsCStr,
     {
         self.create_snapshot_with_progress(
             name,
@@ -346,24 +326,18 @@ impl RemoteFile {
         mut progress: P,
     ) -> Result<Ref<RemoteSnapshot>, ()>
     where
-        S: BnStrCompatible,
+        S: AsCStr,
         P: ProgressCallback,
         I: IntoIterator,
-        I::Item: BnStrCompatible,
+        I::Item: AsCStr,
     {
-        let name = name.into_bytes_with_nul();
-        let parent_ids: Vec<_> = parent_ids
-            .into_iter()
-            .map(|id| id.into_bytes_with_nul())
-            .collect();
-        let mut parent_ids_raw: Vec<_> = parent_ids
-            .iter()
-            .map(|x| x.as_ref().as_ptr() as *const c_char)
-            .collect();
+        let parent_ids = parent_ids.into_iter().collect::<Vec<_>>();
+        let parent_ids = parent_ids.iter().map(|id| id.as_cstr()).collect::<Vec<_>>();
+        let mut parent_ids_raw = parent_ids.iter().map(|x| x.as_ptr()).collect::<Vec<_>>();
         let result = unsafe {
             BNRemoteFileCreateSnapshot(
                 self.handle.as_ptr(),
-                name.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
                 contents.as_mut_ptr(),
                 contents.len(),
                 analysis_cache_contexts.as_mut_ptr(),
@@ -428,10 +402,7 @@ impl RemoteFile {
     ///
     /// * `db_path` - File path for saved database
     /// * `progress_function` - Function to call for progress updates
-    pub fn download<S>(&self, db_path: S) -> Result<Ref<FileMetadata>, ()>
-    where
-        S: BnStrCompatible,
-    {
+    pub fn download<S: AsCStr>(&self, db_path: S) -> Result<Ref<FileMetadata>, ()> {
         sync::download_file(self, db_path)
     }
 
@@ -447,14 +418,14 @@ impl RemoteFile {
         progress_function: F,
     ) -> Result<Ref<FileMetadata>, ()>
     where
-        S: BnStrCompatible,
+        S: AsCStr,
         F: ProgressCallback,
     {
         sync::download_file_with_progress(self, db_path, progress_function)
     }
 
     /// Download a remote file and save it to a BNDB at the given `path`, returning the associated [`FileMetadata`].
-    pub fn download_database<S: BnStrCompatible>(&self, path: S) -> Result<Ref<FileMetadata>, ()> {
+    pub fn download_database<S: AsCStr>(&self, path: S) -> Result<Ref<FileMetadata>, ()> {
         let file = self.download(path)?;
         let database = file.database().ok_or(())?;
         self.sync(&database, DatabaseConflictHandlerFail, NoNameChangeset)?;
@@ -464,7 +435,7 @@ impl RemoteFile {
     // TODO: This might be a bad helper... maybe remove...
     // TODO: AsRef<Path>
     /// Download a remote file and save it to a BNDB at the given `path`.
-    pub fn download_database_with_progress<S: BnStrCompatible>(
+    pub fn download_database_with_progress<S: AsCStr>(
         &self,
         path: S,
         progress: impl ProgressCallback,
diff --git a/rust/src/collaboration/folder.rs b/rust/src/collaboration/folder.rs
index 90a85f1cf..2f3731904 100644
--- a/rust/src/collaboration/folder.rs
+++ b/rust/src/collaboration/folder.rs
@@ -1,11 +1,10 @@
 use super::{Remote, RemoteProject};
 use binaryninjacore_sys::*;
-use std::ffi::c_char;
 use std::ptr::NonNull;
 
 use crate::project::folder::ProjectFolder;
 use crate::rc::{CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 #[repr(transparent)]
 pub struct RemoteFolder {
@@ -104,14 +103,9 @@ impl RemoteFolder {
     }
 
     /// Set the display name of the folder. You will need to push the folder to update the remote version.
-    pub fn set_name<S: BnStrCompatible>(&self, name: S) -> Result<(), ()> {
-        let name = name.into_bytes_with_nul();
-        let success = unsafe {
-            BNRemoteFolderSetName(
-                self.handle.as_ptr(),
-                name.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    pub fn set_name<S: AsCStr>(&self, name: S) -> Result<(), ()> {
+        let success =
+            unsafe { BNRemoteFolderSetName(self.handle.as_ptr(), name.as_cstr().as_ptr()) };
         success.then_some(()).ok_or(())
     }
 
@@ -123,13 +117,9 @@ impl RemoteFolder {
     }
 
     /// Set the description of the folder. You will need to push the folder to update the remote version.
-    pub fn set_description<S: BnStrCompatible>(&self, description: S) -> Result<(), ()> {
-        let description = description.into_bytes_with_nul();
+    pub fn set_description<S: AsCStr>(&self, description: S) -> Result<(), ()> {
         let success = unsafe {
-            BNRemoteFolderSetDescription(
-                self.handle.as_ptr(),
-                description.as_ref().as_ptr() as *const c_char,
-            )
+            BNRemoteFolderSetDescription(self.handle.as_ptr(), description.as_cstr().as_ptr())
         };
         success.then_some(()).ok_or(())
     }
diff --git a/rust/src/collaboration/group.rs b/rust/src/collaboration/group.rs
index bad09d6cb..b79daf9bd 100644
--- a/rust/src/collaboration/group.rs
+++ b/rust/src/collaboration/group.rs
@@ -1,8 +1,7 @@
 use super::Remote;
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use binaryninjacore_sys::*;
-use std::ffi::c_char;
 use std::fmt;
 use std::fmt::{Display, Formatter};
 use std::ptr::NonNull;
@@ -50,14 +49,8 @@ impl RemoteGroup {
 
     /// Set group name
     /// You will need to push the group to update the Remote.
-    pub fn set_name<U: BnStrCompatible>(&self, name: U) {
-        let name = name.into_bytes_with_nul();
-        unsafe {
-            BNCollaborationGroupSetName(
-                self.handle.as_ptr(),
-                name.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn set_name<U: AsCStr>(&self, name: U) {
+        unsafe { BNCollaborationGroupSetName(self.handle.as_ptr(), name.as_cstr().as_ptr()) }
     }
 
     /// Get list of users in the group
@@ -90,16 +83,11 @@ impl RemoteGroup {
     pub fn set_users<I>(&self, usernames: I) -> Result<(), ()>
     where
         I: IntoIterator,
-        I::Item: BnStrCompatible,
+        I::Item: AsCStr,
     {
-        let usernames: Vec<_> = usernames
-            .into_iter()
-            .map(|u| u.into_bytes_with_nul())
-            .collect();
-        let mut usernames_raw: Vec<_> = usernames
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect();
+        let usernames = usernames.into_iter().collect::<Vec<_>>();
+        let usernames = usernames.iter().map(|u| u.as_cstr()).collect::<Vec<_>>();
+        let mut usernames_raw = usernames.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
         // TODO: This should only fail if collaboration is not supported.
         // TODO: Because you should not have a RemoteGroup at that point we can ignore?
         // TODO: Do you need any permissions to do this?
@@ -114,13 +102,9 @@ impl RemoteGroup {
     }
 
     /// Test if a group has a user with the given username
-    pub fn contains_user<U: BnStrCompatible>(&self, username: U) -> bool {
-        let username = username.into_bytes_with_nul();
+    pub fn contains_user<U: AsCStr>(&self, username: U) -> bool {
         unsafe {
-            BNCollaborationGroupContainsUser(
-                self.handle.as_ptr(),
-                username.as_ref().as_ptr() as *const c_char,
-            )
+            BNCollaborationGroupContainsUser(self.handle.as_ptr(), username.as_cstr().as_ptr())
         }
     }
 }
diff --git a/rust/src/collaboration/merge.rs b/rust/src/collaboration/merge.rs
index 2d28725c2..00cfb90d1 100644
--- a/rust/src/collaboration/merge.rs
+++ b/rust/src/collaboration/merge.rs
@@ -1,11 +1,10 @@
 use binaryninjacore_sys::*;
-use std::ffi::c_char;
 use std::ptr::NonNull;
 
 use crate::database::{snapshot::Snapshot, Database};
 use crate::file_metadata::FileMetadata;
 use crate::rc::{CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 pub type MergeConflictDataType = BNMergeConflictDataType;
 
@@ -49,13 +48,9 @@ impl MergeConflict {
         NonNull::new(result).map(|handle| unsafe { Snapshot::from_raw(handle) })
     }
 
-    pub fn path_item_string<S: BnStrCompatible>(&self, path: S) -> Result<BnString, ()> {
-        let path = path.into_bytes_with_nul();
+    pub fn path_item_string<S: AsCStr>(&self, path: S) -> Result<BnString, ()> {
         let result = unsafe {
-            BNAnalysisMergeConflictGetPathItemString(
-                self.handle.as_ptr(),
-                path.as_ref().as_ptr() as *const c_char,
-            )
+            BNAnalysisMergeConflictGetPathItemString(self.handle.as_ptr(), path.as_cstr().as_ptr())
         };
         (!result.is_null())
             .then(|| unsafe { BnString::from_raw(result) })
@@ -123,25 +118,17 @@ impl MergeConflict {
     }
 
     /// Call this when you've resolved the conflict to save the result
-    pub fn success<S: BnStrCompatible>(&self, value: S) -> Result<(), ()> {
-        let value = value.into_bytes_with_nul();
+    pub fn success<S: AsCStr>(&self, value: S) -> Result<(), ()> {
         let success = unsafe {
-            BNAnalysisMergeConflictSuccess(
-                self.handle.as_ptr(),
-                value.as_ref().as_ptr() as *const c_char,
-            )
+            BNAnalysisMergeConflictSuccess(self.handle.as_ptr(), value.as_cstr().as_ptr())
         };
         success.then_some(()).ok_or(())
     }
 
     // TODO: Make a safe version of this that checks the path and if it holds a number
-    pub unsafe fn get_path_item_number<S: BnStrCompatible>(&self, path_key: S) -> Option<u64> {
-        let path_key = path_key.into_bytes_with_nul();
+    pub unsafe fn get_path_item_number<S: AsCStr>(&self, path_key: S) -> Option<u64> {
         let value = unsafe {
-            BNAnalysisMergeConflictGetPathItem(
-                self.handle.as_ptr(),
-                path_key.as_ref().as_ptr() as *const c_char,
-            )
+            BNAnalysisMergeConflictGetPathItem(self.handle.as_ptr(), path_key.as_cstr().as_ptr())
         };
         match value.is_null() {
             // SAFETY: The path must be a number.
@@ -150,12 +137,11 @@ impl MergeConflict {
         }
     }
 
-    pub unsafe fn get_path_item_string<S: BnStrCompatible>(&self, path_key: S) -> Option<BnString> {
-        let path_key = path_key.into_bytes_with_nul();
+    pub unsafe fn get_path_item_string<S: AsCStr>(&self, path_key: S) -> Option<BnString> {
         let value = unsafe {
             BNAnalysisMergeConflictGetPathItemString(
                 self.handle.as_ptr(),
-                path_key.as_ref().as_ptr() as *const c_char,
+                path_key.as_cstr().as_ptr(),
             )
         };
         match value.is_null() {
diff --git a/rust/src/collaboration/project.rs b/rust/src/collaboration/project.rs
index 1455f6c31..3f5f9a2fd 100644
--- a/rust/src/collaboration/project.rs
+++ b/rust/src/collaboration/project.rs
@@ -1,4 +1,4 @@
-use std::ffi::{c_char, c_void};
+use std::ffi::c_void;
 use std::ptr::NonNull;
 use std::time::SystemTime;
 
@@ -15,7 +15,7 @@ use crate::file_metadata::FileMetadata;
 use crate::progress::{NoProgressCallback, ProgressCallback};
 use crate::project::Project;
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 #[repr(transparent)]
 pub struct RemoteProject {
@@ -136,14 +136,9 @@ impl RemoteProject {
     }
 
     /// Set the description of the file. You will need to push the file to update the remote version.
-    pub fn set_name<S: BnStrCompatible>(&self, name: S) -> Result<(), ()> {
-        let name = name.into_bytes_with_nul();
-        let success = unsafe {
-            BNRemoteProjectSetName(
-                self.handle.as_ptr(),
-                name.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    pub fn set_name<S: AsCStr>(&self, name: S) -> Result<(), ()> {
+        let success =
+            unsafe { BNRemoteProjectSetName(self.handle.as_ptr(), name.as_cstr().as_ptr()) };
         success.then_some(()).ok_or(())
     }
 
@@ -155,13 +150,9 @@ impl RemoteProject {
     }
 
     /// Set the description of the file. You will need to push the file to update the remote version.
-    pub fn set_description<S: BnStrCompatible>(&self, description: S) -> Result<(), ()> {
-        let description = description.into_bytes_with_nul();
+    pub fn set_description<S: AsCStr>(&self, description: S) -> Result<(), ()> {
         let success = unsafe {
-            BNRemoteProjectSetDescription(
-                self.handle.as_ptr(),
-                description.as_ref().as_ptr() as *const c_char,
-            )
+            BNRemoteProjectSetDescription(self.handle.as_ptr(), description.as_cstr().as_ptr())
         };
         success.then_some(()).ok_or(())
     }
@@ -230,15 +221,13 @@ impl RemoteProject {
     ///
     /// NOTE: If the project has not been opened, it will be opened upon calling this.
     /// NOTE: If files have not been pulled, they will be pulled upon calling this.
-    pub fn get_file_by_id<S: BnStrCompatible>(&self, id: S) -> Result<Option<Ref<RemoteFile>>, ()> {
+    pub fn get_file_by_id<S: AsCStr>(&self, id: S) -> Result<Option<Ref<RemoteFile>>, ()> {
         // TODO: This sync should be removed?
         if !self.has_pulled_files() {
             self.pull_files()?;
         }
-        let id = id.into_bytes_with_nul();
-        let result = unsafe {
-            BNRemoteProjectGetFileById(self.handle.as_ptr(), id.as_ref().as_ptr() as *const c_char)
-        };
+        let result =
+            unsafe { BNRemoteProjectGetFileById(self.handle.as_ptr(), id.as_cstr().as_ptr()) };
         Ok(NonNull::new(result).map(|handle| unsafe { RemoteFile::ref_from_raw(handle) }))
     }
 
@@ -246,21 +235,13 @@ impl RemoteProject {
     ///
     /// NOTE: If the project has not been opened, it will be opened upon calling this.
     /// NOTE: If files have not been pulled, they will be pulled upon calling this.
-    pub fn get_file_by_name<S: BnStrCompatible>(
-        &self,
-        name: S,
-    ) -> Result<Option<Ref<RemoteFile>>, ()> {
+    pub fn get_file_by_name<S: AsCStr>(&self, name: S) -> Result<Option<Ref<RemoteFile>>, ()> {
         // TODO: This sync should be removed?
         if !self.has_pulled_files() {
             self.pull_files()?;
         }
-        let id = name.into_bytes_with_nul();
-        let result = unsafe {
-            BNRemoteProjectGetFileByName(
-                self.handle.as_ptr(),
-                id.as_ref().as_ptr() as *const c_char,
-            )
-        };
+        let result =
+            unsafe { BNRemoteProjectGetFileByName(self.handle.as_ptr(), name.as_cstr().as_ptr()) };
         Ok(NonNull::new(result).map(|handle| unsafe { RemoteFile::ref_from_raw(handle) }))
     }
 
@@ -311,9 +292,9 @@ impl RemoteProject {
         file_type: RemoteFileType,
     ) -> Result<Ref<RemoteFile>, ()>
     where
-        F: BnStrCompatible,
-        N: BnStrCompatible,
-        D: BnStrCompatible,
+        F: AsCStr,
+        N: AsCStr,
+        D: AsCStr,
     {
         self.create_file_with_progress(
             filename,
@@ -348,26 +329,23 @@ impl RemoteProject {
         mut progress: P,
     ) -> Result<Ref<RemoteFile>, ()>
     where
-        F: BnStrCompatible,
-        N: BnStrCompatible,
-        D: BnStrCompatible,
+        F: AsCStr,
+        N: AsCStr,
+        D: AsCStr,
         P: ProgressCallback,
     {
         // TODO: This sync should be removed?
         self.open()?;
 
-        let filename = filename.into_bytes_with_nul();
-        let name = name.into_bytes_with_nul();
-        let description = description.into_bytes_with_nul();
         let folder_handle = parent_folder.map_or(std::ptr::null_mut(), |f| f.handle.as_ptr());
         let file_ptr = unsafe {
             BNRemoteProjectCreateFile(
                 self.handle.as_ptr(),
-                filename.as_ref().as_ptr() as *const c_char,
+                filename.as_cstr().as_ptr(),
                 contents.as_ptr() as *mut _,
                 contents.len(),
-                name.as_ref().as_ptr() as *const c_char,
-                description.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
+                description.as_cstr().as_ptr(),
                 folder_handle,
                 file_type,
                 Some(P::cb_progress_callback),
@@ -386,24 +364,20 @@ impl RemoteProject {
     pub fn push_file<I, K, V>(&self, file: &RemoteFile, extra_fields: I) -> Result<(), ()>
     where
         I: Iterator<Item = (K, V)>,
-        K: BnStrCompatible,
-        V: BnStrCompatible,
+        K: AsCStr,
+        V: AsCStr,
     {
         // TODO: This sync should be removed?
         self.open()?;
 
-        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
-            .into_iter()
-            .map(|(k, v)| (k.into_bytes_with_nul(), v.into_bytes_with_nul()))
-            .unzip();
-        let mut keys_raw = keys
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect::<Vec<_>>();
-        let mut values_raw = values
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect::<Vec<_>>();
+        let (keys, values): (Vec<_>, Vec<_>) = extra_fields.into_iter().unzip();
+
+        let keys = keys.iter().map(|k| k.as_cstr()).collect::<Vec<_>>();
+        let mut keys_raw = keys.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
+
+        let values = values.iter().map(|v| v.as_cstr()).collect::<Vec<_>>();
+        let mut values_raw = values.iter().map(|v| v.as_ptr()).collect::<Vec<_>>();
+
         let success = unsafe {
             BNRemoteProjectPushFile(
                 self.handle.as_ptr(),
@@ -446,21 +420,13 @@ impl RemoteProject {
     ///
     /// NOTE: If the project has not been opened, it will be opened upon calling this.
     /// NOTE: If folders have not been pulled, they will be pulled upon calling this.
-    pub fn get_folder_by_id<S: BnStrCompatible>(
-        &self,
-        id: S,
-    ) -> Result<Option<Ref<RemoteFolder>>, ()> {
+    pub fn get_folder_by_id<S: AsCStr>(&self, id: S) -> Result<Option<Ref<RemoteFolder>>, ()> {
         // TODO: This sync should be removed?
         if !self.has_pulled_folders() {
             self.pull_folders()?;
         }
-        let id = id.into_bytes_with_nul();
-        let result = unsafe {
-            BNRemoteProjectGetFolderById(
-                self.handle.as_ptr(),
-                id.as_ref().as_ptr() as *const c_char,
-            )
-        };
+        let result =
+            unsafe { BNRemoteProjectGetFolderById(self.handle.as_ptr(), id.as_cstr().as_ptr()) };
         Ok(NonNull::new(result).map(|handle| unsafe { RemoteFolder::ref_from_raw(handle) }))
     }
 
@@ -505,8 +471,8 @@ impl RemoteProject {
         parent_folder: Option<&RemoteFolder>,
     ) -> Result<Ref<RemoteFolder>, ()>
     where
-        N: BnStrCompatible,
-        D: BnStrCompatible,
+        N: AsCStr,
+        D: AsCStr,
     {
         self.create_folder_with_progress(name, description, parent_folder, NoProgressCallback)
     }
@@ -527,21 +493,19 @@ impl RemoteProject {
         mut progress: P,
     ) -> Result<Ref<RemoteFolder>, ()>
     where
-        N: BnStrCompatible,
-        D: BnStrCompatible,
+        N: AsCStr,
+        D: AsCStr,
         P: ProgressCallback,
     {
         // TODO: This sync should be removed?
         self.open()?;
 
-        let name = name.into_bytes_with_nul();
-        let description = description.into_bytes_with_nul();
         let folder_handle = parent_folder.map_or(std::ptr::null_mut(), |f| f.handle.as_ptr());
         let file_ptr = unsafe {
             BNRemoteProjectCreateFolder(
                 self.handle.as_ptr(),
-                name.as_ref().as_ptr() as *const c_char,
-                description.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
+                description.as_cstr().as_ptr(),
                 folder_handle,
                 Some(P::cb_progress_callback),
                 &mut progress as *mut P as *mut c_void,
@@ -562,24 +526,20 @@ impl RemoteProject {
     pub fn push_folder<I, K, V>(&self, folder: &RemoteFolder, extra_fields: I) -> Result<(), ()>
     where
         I: Iterator<Item = (K, V)>,
-        K: BnStrCompatible,
-        V: BnStrCompatible,
+        K: AsCStr,
+        V: AsCStr,
     {
         // TODO: This sync should be removed?
         self.open()?;
 
-        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
-            .into_iter()
-            .map(|(k, v)| (k.into_bytes_with_nul(), v.into_bytes_with_nul()))
-            .unzip();
-        let mut keys_raw = keys
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect::<Vec<_>>();
-        let mut values_raw = values
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect::<Vec<_>>();
+        let (keys, values): (Vec<_>, Vec<_>) = extra_fields.into_iter().unzip();
+
+        let keys = keys.iter().map(|k| k.as_cstr()).collect::<Vec<_>>();
+        let mut keys_raw = keys.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
+
+        let values = values.iter().map(|v| v.as_cstr()).collect::<Vec<_>>();
+        let mut values_raw = values.iter().map(|v| v.as_ptr()).collect::<Vec<_>>();
+
         let success = unsafe {
             BNRemoteProjectPushFolder(
                 self.handle.as_ptr(),
@@ -637,10 +597,7 @@ impl RemoteProject {
     /// Get a specific permission in the Project by its id.
     ///
     /// NOTE: If group or user permissions have not been pulled, they will be pulled upon calling this.
-    pub fn get_permission_by_id<S: BnStrCompatible>(
-        &self,
-        id: S,
-    ) -> Result<Option<Ref<Permission>>, ()> {
+    pub fn get_permission_by_id<S: AsCStr>(&self, id: S) -> Result<Option<Ref<Permission>>, ()> {
         // TODO: This sync should be removed?
         if !self.has_pulled_user_permissions() {
             self.pull_user_permissions()?;
@@ -650,9 +607,8 @@ impl RemoteProject {
             self.pull_group_permissions()?;
         }
 
-        let id = id.into_bytes_with_nul();
         let value = unsafe {
-            BNRemoteProjectGetPermissionById(self.handle.as_ptr(), id.as_ref().as_ptr() as *const _)
+            BNRemoteProjectGetPermissionById(self.handle.as_ptr(), id.as_cstr().as_ptr())
         };
         Ok(NonNull::new(value).map(|v| unsafe { Permission::ref_from_raw(v) }))
     }
@@ -745,7 +701,7 @@ impl RemoteProject {
     ///
     /// * `user_id` - User id
     /// * `level` - Permission level
-    pub fn create_user_permission<S: BnStrCompatible>(
+    pub fn create_user_permission<S: AsCStr>(
         &self,
         user_id: S,
         level: CollaborationPermissionLevel,
@@ -760,17 +716,16 @@ impl RemoteProject {
     /// * `user_id` - User id
     /// * `level` - Permission level
     /// * `progress` - The progress callback to call
-    pub fn create_user_permission_with_progress<S: BnStrCompatible, F: ProgressCallback>(
+    pub fn create_user_permission_with_progress<S: AsCStr, F: ProgressCallback>(
         &self,
         user_id: S,
         level: CollaborationPermissionLevel,
         mut progress: F,
     ) -> Result<Ref<Permission>, ()> {
-        let user_id = user_id.into_bytes_with_nul();
         let value = unsafe {
             BNRemoteProjectCreateUserPermission(
                 self.handle.as_ptr(),
-                user_id.as_ref().as_ptr() as *const c_char,
+                user_id.as_cstr().as_ptr(),
                 level,
                 Some(F::cb_progress_callback),
                 &mut progress as *mut F as *mut c_void,
@@ -795,21 +750,16 @@ impl RemoteProject {
     ) -> Result<(), ()>
     where
         I: Iterator<Item = (K, V)>,
-        K: BnStrCompatible,
-        V: BnStrCompatible,
+        K: AsCStr,
+        V: AsCStr,
     {
-        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
-            .into_iter()
-            .map(|(k, v)| (k.into_bytes_with_nul(), v.into_bytes_with_nul()))
-            .unzip();
-        let mut keys_raw = keys
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect::<Vec<_>>();
-        let mut values_raw = values
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect::<Vec<_>>();
+        let (keys, values): (Vec<_>, Vec<_>) = extra_fields.into_iter().unzip();
+
+        let keys = keys.iter().map(|k| k.as_cstr()).collect::<Vec<_>>();
+        let mut keys_raw = keys.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
+
+        let values = values.iter().map(|v| v.as_cstr()).collect::<Vec<_>>();
+        let mut values_raw = values.iter().map(|v| v.as_ptr()).collect::<Vec<_>>();
 
         let success = unsafe {
             BNRemoteProjectPushPermission(
@@ -836,14 +786,8 @@ impl RemoteProject {
     /// # Arguments
     ///
     /// * `username` - Username of user to check
-    pub fn can_user_view<S: BnStrCompatible>(&self, username: S) -> bool {
-        let username = username.into_bytes_with_nul();
-        unsafe {
-            BNRemoteProjectCanUserView(
-                self.handle.as_ptr(),
-                username.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn can_user_view<S: AsCStr>(&self, username: S) -> bool {
+        unsafe { BNRemoteProjectCanUserView(self.handle.as_ptr(), username.as_cstr().as_ptr()) }
     }
 
     /// Determine if a user is in any of the edit/admin groups.
@@ -851,14 +795,8 @@ impl RemoteProject {
     /// # Arguments
     ///
     /// * `username` - Username of user to check
-    pub fn can_user_edit<S: BnStrCompatible>(&self, username: S) -> bool {
-        let username = username.into_bytes_with_nul();
-        unsafe {
-            BNRemoteProjectCanUserEdit(
-                self.handle.as_ptr(),
-                username.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn can_user_edit<S: AsCStr>(&self, username: S) -> bool {
+        unsafe { BNRemoteProjectCanUserEdit(self.handle.as_ptr(), username.as_cstr().as_ptr()) }
     }
 
     /// Determine if a user is in the admin group.
@@ -866,14 +804,8 @@ impl RemoteProject {
     /// # Arguments
     ///
     /// * `username` - Username of user to check
-    pub fn can_user_admin<S: BnStrCompatible>(&self, username: S) -> bool {
-        let username = username.into_bytes_with_nul();
-        unsafe {
-            BNRemoteProjectCanUserAdmin(
-                self.handle.as_ptr(),
-                username.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn can_user_admin<S: AsCStr>(&self, username: S) -> bool {
+        unsafe { BNRemoteProjectCanUserAdmin(self.handle.as_ptr(), username.as_cstr().as_ptr()) }
     }
 
     /// Get the default directory path for a remote Project. This is based off
diff --git a/rust/src/collaboration/remote.rs b/rust/src/collaboration/remote.rs
index c12504033..7932b07fc 100644
--- a/rust/src/collaboration/remote.rs
+++ b/rust/src/collaboration/remote.rs
@@ -1,5 +1,5 @@
 use binaryninjacore_sys::*;
-use std::ffi::{c_char, c_void};
+use std::ffi::c_void;
 use std::ptr::NonNull;
 
 use super::{sync, GroupId, RemoteGroup, RemoteProject, RemoteUser};
@@ -10,7 +10,7 @@ use crate::enterprise;
 use crate::progress::{NoProgressCallback, ProgressCallback};
 use crate::project::Project;
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 #[repr(transparent)]
 pub struct Remote {
@@ -27,14 +27,9 @@ impl Remote {
     }
 
     /// Create a Remote and add it to the list of known remotes (saved to Settings)
-    pub fn new<N: BnStrCompatible, A: BnStrCompatible>(name: N, address: A) -> Ref<Self> {
-        let name = name.into_bytes_with_nul();
-        let address = address.into_bytes_with_nul();
+    pub fn new<N: AsCStr, A: AsCStr>(name: N, address: A) -> Ref<Self> {
         let result = unsafe {
-            BNCollaborationCreateRemote(
-                name.as_ref().as_ptr() as *const c_char,
-                address.as_ref().as_ptr() as *const c_char,
-            )
+            BNCollaborationCreateRemote(name.as_cstr().as_ptr(), address.as_cstr().as_ptr())
         };
         unsafe { Self::ref_from_raw(NonNull::new(result).unwrap()) }
     }
@@ -168,18 +163,16 @@ impl Remote {
     }
 
     /// Requests an authentication token using a username and password.
-    pub fn request_authentication_token<U: BnStrCompatible, P: BnStrCompatible>(
+    pub fn request_authentication_token<U: AsCStr, P: AsCStr>(
         &self,
         username: U,
         password: P,
     ) -> Option<BnString> {
-        let username = username.into_bytes_with_nul();
-        let password = password.into_bytes_with_nul();
         let token = unsafe {
             BNRemoteRequestAuthenticationToken(
                 self.handle.as_ptr(),
-                username.as_ref().as_ptr() as *const c_char,
-                password.as_ref().as_ptr() as *const c_char,
+                username.as_cstr().as_ptr(),
+                password.as_cstr().as_ptr(),
             )
         };
         if token.is_null() {
@@ -229,11 +222,13 @@ impl Remote {
                 token.unwrap().to_string()
             }
         };
-        let username = options.username.into_bytes_with_nul();
-        let username_ptr = username.as_ptr() as *const c_char;
-        let token = token.into_bytes_with_nul();
-        let token_ptr = token.as_ptr() as *const c_char;
-        let success = unsafe { BNRemoteConnect(self.handle.as_ptr(), username_ptr, token_ptr) };
+        let success = unsafe {
+            BNRemoteConnect(
+                self.handle.as_ptr(),
+                options.username.as_cstr().as_ptr(),
+                token.as_cstr().as_ptr(),
+            )
+        };
         success.then_some(()).ok_or(())
     }
 
@@ -281,25 +276,19 @@ impl Remote {
     /// Gets a specific project in the Remote by its id.
     ///
     /// NOTE: If projects have not been pulled, they will be pulled upon calling this.
-    pub fn get_project_by_id<S: BnStrCompatible>(
-        &self,
-        id: S,
-    ) -> Result<Option<Ref<RemoteProject>>, ()> {
+    pub fn get_project_by_id<S: AsCStr>(&self, id: S) -> Result<Option<Ref<RemoteProject>>, ()> {
         if !self.has_pulled_projects() {
             self.pull_projects()?;
         }
 
-        let id = id.into_bytes_with_nul();
-        let value = unsafe {
-            BNRemoteGetProjectById(self.handle.as_ptr(), id.as_ref().as_ptr() as *const c_char)
-        };
+        let value = unsafe { BNRemoteGetProjectById(self.handle.as_ptr(), id.as_cstr().as_ptr()) };
         Ok(NonNull::new(value).map(|handle| unsafe { RemoteProject::ref_from_raw(handle) }))
     }
 
     /// Gets a specific project in the Remote by its name.
     ///
     /// NOTE: If projects have not been pulled, they will be pulled upon calling this.
-    pub fn get_project_by_name<S: BnStrCompatible>(
+    pub fn get_project_by_name<S: AsCStr>(
         &self,
         name: S,
     ) -> Result<Option<Ref<RemoteProject>>, ()> {
@@ -307,13 +296,8 @@ impl Remote {
             self.pull_projects()?;
         }
 
-        let name = name.into_bytes_with_nul();
-        let value = unsafe {
-            BNRemoteGetProjectByName(
-                self.handle.as_ptr(),
-                name.as_ref().as_ptr() as *const c_char,
-            )
-        };
+        let value =
+            unsafe { BNRemoteGetProjectByName(self.handle.as_ptr(), name.as_cstr().as_ptr()) };
         Ok(NonNull::new(value).map(|handle| unsafe { RemoteProject::ref_from_raw(handle) }))
     }
 
@@ -347,7 +331,7 @@ impl Remote {
     ///
     /// * `name` - Project name
     /// * `description` - Project description
-    pub fn create_project<N: BnStrCompatible, D: BnStrCompatible>(
+    pub fn create_project<N: AsCStr, D: AsCStr>(
         &self,
         name: N,
         description: D,
@@ -358,13 +342,11 @@ impl Remote {
         if !self.has_pulled_projects() {
             self.pull_projects()?;
         }
-        let name = name.into_bytes_with_nul();
-        let description = description.into_bytes_with_nul();
         let value = unsafe {
             BNRemoteCreateProject(
                 self.handle.as_ptr(),
-                name.as_ref().as_ptr() as *const c_char,
-                description.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
+                description.as_cstr().as_ptr(),
             )
         };
         NonNull::new(value)
@@ -403,21 +385,16 @@ impl Remote {
     pub fn push_project<I, K, V>(&self, project: &RemoteProject, extra_fields: I) -> Result<(), ()>
     where
         I: Iterator<Item = (K, V)>,
-        K: BnStrCompatible,
-        V: BnStrCompatible,
+        K: AsCStr,
+        V: AsCStr,
     {
-        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
-            .into_iter()
-            .map(|(k, v)| (k.into_bytes_with_nul(), v.into_bytes_with_nul()))
-            .unzip();
-        let mut keys_raw = keys
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect::<Vec<_>>();
-        let mut values_raw = values
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect::<Vec<_>>();
+        let (keys, values): (Vec<_>, Vec<_>) = extra_fields.into_iter().unzip();
+
+        let keys = keys.iter().map(|k| k.as_cstr()).collect::<Vec<_>>();
+        let mut keys_raw = keys.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
+
+        let values = values.iter().map(|v| v.as_cstr()).collect::<Vec<_>>();
+        let mut values_raw = values.iter().map(|v| v.as_ptr()).collect::<Vec<_>>();
 
         let success = unsafe {
             BNRemotePushProject(
@@ -472,21 +449,13 @@ impl Remote {
     ///
     /// If groups have not been pulled, they will be pulled upon calling this.
     /// This function is only available to accounts with admin status on the Remote.
-    pub fn get_group_by_name<S: BnStrCompatible>(
-        &self,
-        name: S,
-    ) -> Result<Option<Ref<RemoteGroup>>, ()> {
+    pub fn get_group_by_name<S: AsCStr>(&self, name: S) -> Result<Option<Ref<RemoteGroup>>, ()> {
         if !self.has_pulled_groups() {
             self.pull_groups()?;
         }
 
-        let name = name.into_bytes_with_nul();
-        let value = unsafe {
-            BNRemoteGetGroupByName(
-                self.handle.as_ptr(),
-                name.as_ref().as_ptr() as *const c_char,
-            )
-        };
+        let value =
+            unsafe { BNRemoteGetGroupByName(self.handle.as_ptr(), name.as_cstr().as_ptr()) };
 
         Ok(NonNull::new(value).map(|handle| unsafe { RemoteGroup::ref_from_raw(handle) }))
     }
@@ -496,11 +465,10 @@ impl Remote {
     /// # Arguments
     ///
     /// * `prefix` - Prefix of name for groups
-    pub fn search_groups<S: BnStrCompatible>(
+    pub fn search_groups<S: AsCStr>(
         &self,
         prefix: S,
     ) -> Result<(Array<GroupId>, Array<BnString>), ()> {
-        let prefix = prefix.into_bytes_with_nul();
         let mut count = 0;
         let mut group_ids = std::ptr::null_mut();
         let mut group_names = std::ptr::null_mut();
@@ -508,7 +476,7 @@ impl Remote {
         let success = unsafe {
             BNRemoteSearchGroups(
                 self.handle.as_ptr(),
-                prefix.as_ref().as_ptr() as *const c_char,
+                prefix.as_cstr().as_ptr(),
                 &mut group_ids,
                 &mut group_names,
                 &mut count,
@@ -560,24 +528,18 @@ impl Remote {
     /// * `usernames` - List of usernames of users in the group
     pub fn create_group<N, I>(&self, name: N, usernames: I) -> Result<Ref<RemoteGroup>, ()>
     where
-        N: BnStrCompatible,
+        N: AsCStr,
         I: IntoIterator,
-        I::Item: BnStrCompatible,
+        I::Item: AsCStr,
     {
-        let name = name.into_bytes_with_nul();
-        let usernames: Vec<_> = usernames
-            .into_iter()
-            .map(|s| s.into_bytes_with_nul())
-            .collect();
-        let mut username_ptrs: Vec<_> = usernames
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect();
+        let usernames = usernames.into_iter().collect::<Vec<_>>();
+        let usernames = usernames.iter().map(|s| s.as_cstr()).collect::<Vec<_>>();
+        let mut username_ptrs = usernames.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
 
         let value = unsafe {
             BNRemoteCreateGroup(
                 self.handle.as_ptr(),
-                name.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
                 username_ptrs.as_mut_ptr(),
                 username_ptrs.len(),
             )
@@ -597,21 +559,16 @@ impl Remote {
     pub fn push_group<I, K, V>(&self, group: &RemoteGroup, extra_fields: I) -> Result<(), ()>
     where
         I: IntoIterator<Item = (K, V)>,
-        K: BnStrCompatible,
-        V: BnStrCompatible,
+        K: AsCStr,
+        V: AsCStr,
     {
-        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
-            .into_iter()
-            .map(|(k, v)| (k.into_bytes_with_nul(), v.into_bytes_with_nul()))
-            .unzip();
-        let mut keys_raw: Vec<_> = keys
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect();
-        let mut values_raw: Vec<_> = values
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect();
+        let (keys, values): (Vec<_>, Vec<_>) = extra_fields.into_iter().unzip();
+
+        let keys = keys.iter().map(|k| k.as_cstr()).collect::<Vec<_>>();
+        let mut keys_raw = keys.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
+
+        let values = values.iter().map(|v| v.as_cstr()).collect::<Vec<_>>();
+        let mut values_raw = values.iter().map(|v| v.as_ptr()).collect::<Vec<_>>();
 
         let success = unsafe {
             BNRemotePushGroup(
@@ -663,14 +620,11 @@ impl Remote {
     /// # Arguments
     ///
     /// * `id` - The identifier of the user to retrieve.
-    pub fn get_user_by_id<S: BnStrCompatible>(&self, id: S) -> Result<Option<Ref<RemoteUser>>, ()> {
+    pub fn get_user_by_id<S: AsCStr>(&self, id: S) -> Result<Option<Ref<RemoteUser>>, ()> {
         if !self.has_pulled_users() {
             self.pull_users()?;
         }
-        let id = id.into_bytes_with_nul();
-        let value = unsafe {
-            BNRemoteGetUserById(self.handle.as_ptr(), id.as_ref().as_ptr() as *const c_char)
-        };
+        let value = unsafe { BNRemoteGetUserById(self.handle.as_ptr(), id.as_cstr().as_ptr()) };
         Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
     }
 
@@ -683,20 +637,15 @@ impl Remote {
     /// # Arguments
     ///
     /// * `username` - The username of the user to retrieve.
-    pub fn get_user_by_username<S: BnStrCompatible>(
+    pub fn get_user_by_username<S: AsCStr>(
         &self,
         username: S,
     ) -> Result<Option<Ref<RemoteUser>>, ()> {
         if !self.has_pulled_users() {
             self.pull_users()?;
         }
-        let username = username.into_bytes_with_nul();
-        let value = unsafe {
-            BNRemoteGetUserByUsername(
-                self.handle.as_ptr(),
-                username.as_ref().as_ptr() as *const c_char,
-            )
-        };
+        let value =
+            unsafe { BNRemoteGetUserByUsername(self.handle.as_ptr(), username.as_cstr().as_ptr()) };
         Ok(NonNull::new(value).map(|handle| unsafe { RemoteUser::ref_from_raw(handle) }))
     }
 
@@ -718,18 +667,17 @@ impl Remote {
     /// # Arguments
     ///
     /// * `prefix` - The prefix to search for in usernames.
-    pub fn search_users<S: BnStrCompatible>(
+    pub fn search_users<S: AsCStr>(
         &self,
         prefix: S,
     ) -> Result<(Array<BnString>, Array<BnString>), ()> {
-        let prefix = prefix.into_bytes_with_nul();
         let mut count = 0;
         let mut user_ids = std::ptr::null_mut();
         let mut usernames = std::ptr::null_mut();
         let success = unsafe {
             BNRemoteSearchUsers(
                 self.handle.as_ptr(),
-                prefix.as_ref().as_ptr() as *const c_char,
+                prefix.as_cstr().as_ptr(),
                 &mut user_ids,
                 &mut usernames,
                 &mut count,
@@ -783,7 +731,7 @@ impl Remote {
     /// # Arguments
     ///
     /// * Various details about the new user to be created.
-    pub fn create_user<U: BnStrCompatible, E: BnStrCompatible, P: BnStrCompatible>(
+    pub fn create_user<U: AsCStr, E: AsCStr, P: AsCStr>(
         &self,
         username: U,
         email: E,
@@ -792,17 +740,13 @@ impl Remote {
         group_ids: &[u64],
         user_permission_ids: &[u64],
     ) -> Result<Ref<RemoteUser>, ()> {
-        let username = username.into_bytes_with_nul();
-        let email = email.into_bytes_with_nul();
-        let password = password.into_bytes_with_nul();
-
         let value = unsafe {
             BNRemoteCreateUser(
                 self.handle.as_ptr(),
-                username.as_ref().as_ptr() as *const c_char,
-                email.as_ref().as_ptr() as *const c_char,
+                username.as_cstr().as_ptr(),
+                email.as_cstr().as_ptr(),
                 is_active,
-                password.as_ref().as_ptr() as *const c_char,
+                password.as_cstr().as_ptr(),
                 group_ids.as_ptr(),
                 group_ids.len(),
                 user_permission_ids.as_ptr(),
@@ -825,21 +769,17 @@ impl Remote {
     pub fn push_user<I, K, V>(&self, user: &RemoteUser, extra_fields: I) -> Result<(), ()>
     where
         I: Iterator<Item = (K, V)>,
-        K: BnStrCompatible,
-        V: BnStrCompatible,
+        K: AsCStr,
+        V: AsCStr,
     {
-        let (keys, values): (Vec<_>, Vec<_>) = extra_fields
-            .into_iter()
-            .map(|(k, v)| (k.into_bytes_with_nul(), v.into_bytes_with_nul()))
-            .unzip();
-        let mut keys_raw: Vec<_> = keys
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect();
-        let mut values_raw: Vec<_> = values
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect();
+        let (keys, values): (Vec<_>, Vec<_>) = extra_fields.into_iter().unzip();
+
+        let keys = keys.iter().map(|k| k.as_cstr()).collect::<Vec<_>>();
+        let mut keys_raw = keys.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
+
+        let values = values.iter().map(|v| v.as_cstr()).collect::<Vec<_>>();
+        let mut values_raw = values.iter().map(|v| v.as_ptr()).collect::<Vec<_>>();
+
         let success = unsafe {
             BNRemotePushUser(
                 self.handle.as_ptr(),
diff --git a/rust/src/collaboration/snapshot.rs b/rust/src/collaboration/snapshot.rs
index 9f8f36934..faa97a25a 100644
--- a/rust/src/collaboration/snapshot.rs
+++ b/rust/src/collaboration/snapshot.rs
@@ -1,4 +1,4 @@
-use std::ffi::{c_char, c_void};
+use std::ffi::c_void;
 use std::ptr::NonNull;
 use std::time::SystemTime;
 
@@ -8,7 +8,7 @@ use crate::collaboration::undo::{RemoteUndoEntry, RemoteUndoEntryId};
 use crate::database::snapshot::Snapshot;
 use crate::progress::{NoProgressCallback, ProgressCallback};
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use binaryninjacore_sys::*;
 
 // TODO: RemoteSnapshotId ?
@@ -226,18 +226,17 @@ impl RemoteSnapshot {
     }
 
     /// Create a new Undo Entry in this snapshot.
-    pub fn create_undo_entry<S: BnStrCompatible>(
+    pub fn create_undo_entry<S: AsCStr>(
         &self,
         parent: Option<u64>,
         data: S,
     ) -> Result<Ref<RemoteUndoEntry>, ()> {
-        let data = data.into_bytes_with_nul();
         let value = unsafe {
             BNCollaborationSnapshotCreateUndoEntry(
                 self.handle.as_ptr(),
                 parent.is_some(),
                 parent.unwrap_or(0),
-                data.as_ref().as_ptr() as *const c_char,
+                data.as_cstr().as_ptr(),
             )
         };
         let handle = NonNull::new(value).ok_or(())?;
diff --git a/rust/src/collaboration/sync.rs b/rust/src/collaboration/sync.rs
index 1c11b8f06..8971f2f5b 100644
--- a/rust/src/collaboration/sync.rs
+++ b/rust/src/collaboration/sync.rs
@@ -12,7 +12,7 @@ use crate::file_metadata::FileMetadata;
 use crate::progress::{NoProgressCallback, ProgressCallback};
 use crate::project::file::ProjectFile;
 use crate::rc::Ref;
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use crate::type_archive::{TypeArchive, TypeArchiveMergeConflict};
 
 // TODO: PathBuf
@@ -44,10 +44,7 @@ pub fn default_file_path(file: &RemoteFile) -> Result<BnString, ()> {
 ///
 /// * `file` - Remote File to download and open
 /// * `db_path` - File path for saved database
-pub fn download_file<S: BnStrCompatible>(
-    file: &RemoteFile,
-    db_path: S,
-) -> Result<Ref<FileMetadata>, ()> {
+pub fn download_file<S: AsCStr>(file: &RemoteFile, db_path: S) -> Result<Ref<FileMetadata>, ()> {
     download_file_with_progress(file, db_path, NoProgressCallback)
 }
 
@@ -58,16 +55,15 @@ pub fn download_file<S: BnStrCompatible>(
 /// * `file` - Remote File to download and open
 /// * `db_path` - File path for saved database
 /// * `progress` - Function to call for progress updates
-pub fn download_file_with_progress<S: BnStrCompatible, F: ProgressCallback>(
+pub fn download_file_with_progress<S: AsCStr, F: ProgressCallback>(
     file: &RemoteFile,
     db_path: S,
     mut progress: F,
 ) -> Result<Ref<FileMetadata>, ()> {
-    let db_path = db_path.into_bytes_with_nul();
     let result = unsafe {
         BNCollaborationDownloadFile(
             file.handle.as_ptr(),
-            db_path.as_ref().as_ptr() as *const c_char,
+            db_path.as_cstr().as_ptr(),
             Some(F::cb_progress_callback),
             &mut progress as *mut F as *mut c_void,
         )
@@ -222,10 +218,7 @@ pub fn get_local_snapshot_for_remote(
         .ok_or(())
 }
 
-pub fn download_database<S>(file: &RemoteFile, location: S, force: bool) -> Result<(), ()>
-where
-    S: BnStrCompatible,
-{
+pub fn download_database<S: AsCStr>(file: &RemoteFile, location: S, force: bool) -> Result<(), ()> {
     download_database_with_progress(file, location, force, NoProgressCallback)
 }
 
@@ -236,14 +229,13 @@ pub fn download_database_with_progress<S, F>(
     mut progress: F,
 ) -> Result<(), ()>
 where
-    S: BnStrCompatible,
+    S: AsCStr,
     F: ProgressCallback,
 {
-    let db_path = location.into_bytes_with_nul();
     let success = unsafe {
         BNCollaborationDownloadDatabaseForFile(
             file.handle.as_ptr(),
-            db_path.as_ref().as_ptr() as *const c_char,
+            location.as_cstr().as_ptr(),
             force,
             Some(F::cb_progress_callback),
             &mut progress as *mut _ as *mut c_void,
@@ -479,17 +471,16 @@ pub fn get_snapshot_author(
 /// * `database` - Parent database
 /// * `snapshot` - Snapshot to edit
 /// * `author` - Target author
-pub fn set_snapshot_author<S: BnStrCompatible>(
+pub fn set_snapshot_author<S: AsCStr>(
     database: &Database,
     snapshot: &Snapshot,
     author: S,
 ) -> Result<(), ()> {
-    let author = author.into_bytes_with_nul();
     let success = unsafe {
         BNCollaborationSetSnapshotAuthor(
             database.handle.as_ptr(),
             snapshot.handle.as_ptr(),
-            author.as_ref().as_ptr() as *const c_char,
+            author.as_cstr().as_ptr(),
         )
     };
     success.then_some(()).ok_or(())
@@ -654,15 +645,14 @@ pub fn get_remote_file_for_local_type_archive(database: &TypeArchive) -> Option<
 }
 
 /// Get the remote snapshot associated with a local snapshot (if it exists) in a Type Archive
-pub fn get_remote_snapshot_from_local_type_archive<S: BnStrCompatible>(
+pub fn get_remote_snapshot_from_local_type_archive<S: AsCStr>(
     type_archive: &TypeArchive,
     snapshot_id: S,
 ) -> Option<Ref<RemoteSnapshot>> {
-    let snapshot_id = snapshot_id.into_bytes_with_nul();
     let value = unsafe {
         BNCollaborationGetRemoteSnapshotFromLocalTypeArchive(
             type_archive.handle.as_ptr(),
-            snapshot_id.as_ref().as_ptr() as *const c_char,
+            snapshot_id.as_cstr().as_ptr(),
         )
     };
     NonNull::new(value).map(|handle| unsafe { RemoteSnapshot::ref_from_raw(handle) })
@@ -683,22 +673,21 @@ pub fn get_local_snapshot_from_remote_type_archive(
 }
 
 /// Test if a snapshot is ignored from the archive
-pub fn is_type_archive_snapshot_ignored<S: BnStrCompatible>(
+pub fn is_type_archive_snapshot_ignored<S: AsCStr>(
     type_archive: &TypeArchive,
     snapshot_id: S,
 ) -> bool {
-    let snapshot_id = snapshot_id.into_bytes_with_nul();
     unsafe {
         BNCollaborationIsTypeArchiveSnapshotIgnored(
             type_archive.handle.as_ptr(),
-            snapshot_id.as_ref().as_ptr() as *const c_char,
+            snapshot_id.as_cstr().as_ptr(),
         )
     }
 }
 
 /// Download a type archive from its remote, saving all snapshots to an archive in the
 /// specified `location`. Returns a [`TypeArchive`] for using later.
-pub fn download_type_archive<S: BnStrCompatible>(
+pub fn download_type_archive<S: AsCStr>(
     file: &RemoteFile,
     location: S,
 ) -> Result<Option<Ref<TypeArchive>>, ()> {
@@ -707,17 +696,16 @@ pub fn download_type_archive<S: BnStrCompatible>(
 
 /// Download a type archive from its remote, saving all snapshots to an archive in the
 /// specified `location`. Returns a [`TypeArchive`] for using later.
-pub fn download_type_archive_with_progress<S: BnStrCompatible, F: ProgressCallback>(
+pub fn download_type_archive_with_progress<S: AsCStr, F: ProgressCallback>(
     file: &RemoteFile,
     location: S,
     mut progress: F,
 ) -> Result<Option<Ref<TypeArchive>>, ()> {
     let mut value = std::ptr::null_mut();
-    let db_path = location.into_bytes_with_nul();
     let success = unsafe {
         BNCollaborationDownloadTypeArchive(
             file.handle.as_ptr(),
-            db_path.as_ref().as_ptr() as *const c_char,
+            location.as_cstr().as_ptr(),
             Some(F::cb_progress_callback),
             &mut progress as *mut F as *mut c_void,
             &mut value,
diff --git a/rust/src/collaboration/user.rs b/rust/src/collaboration/user.rs
index 0e3433d31..779aef28d 100644
--- a/rust/src/collaboration/user.rs
+++ b/rust/src/collaboration/user.rs
@@ -1,10 +1,9 @@
 use super::Remote;
 use binaryninjacore_sys::*;
-use std::ffi::c_char;
 use std::ptr::NonNull;
 
 use crate::rc::{CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 #[repr(transparent)]
 pub struct RemoteUser {
@@ -49,13 +48,9 @@ impl RemoteUser {
     }
 
     /// Set user's username. You will need to push the user to update the Remote
-    pub fn set_username<U: BnStrCompatible>(&self, username: U) -> Result<(), ()> {
-        let username = username.into_bytes_with_nul();
+    pub fn set_username<U: AsCStr>(&self, username: U) -> Result<(), ()> {
         let result = unsafe {
-            BNCollaborationUserSetUsername(
-                self.handle.as_ptr(),
-                username.as_ref().as_ptr() as *const c_char,
-            )
+            BNCollaborationUserSetUsername(self.handle.as_ptr(), username.as_cstr().as_ptr())
         };
         if result {
             Ok(())
@@ -72,14 +67,9 @@ impl RemoteUser {
     }
 
     /// Set user's email. You will need to push the user to update the Remote
-    pub fn set_email<U: BnStrCompatible>(&self, email: U) -> Result<(), ()> {
-        let username = email.into_bytes_with_nul();
-        let result = unsafe {
-            BNCollaborationUserSetEmail(
-                self.handle.as_ptr(),
-                username.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    pub fn set_email<U: AsCStr>(&self, email: U) -> Result<(), ()> {
+        let result =
+            unsafe { BNCollaborationUserSetEmail(self.handle.as_ptr(), email.as_cstr().as_ptr()) };
         if result {
             Ok(())
         } else {
diff --git a/rust/src/command.rs b/rust/src/command.rs
index 48a911645..d4b902071 100644
--- a/rust/src/command.rs
+++ b/rust/src/command.rs
@@ -42,7 +42,7 @@ use std::os::raw::c_void;
 
 use crate::binary_view::BinaryView;
 use crate::function::Function;
-use crate::string::BnStrCompatible;
+use crate::string::AsCStr;
 
 /// The trait required for generic commands.  See [register_command] for example usage.
 pub trait Command: 'static + Sync {
@@ -95,7 +95,7 @@ where
 /// ```
 pub fn register_command<S, C>(name: S, desc: S, command: C)
 where
-    S: BnStrCompatible,
+    S: AsCStr,
     C: Command,
 {
     extern "C" fn cb_action<C>(ctxt: *mut c_void, view: *mut BNBinaryView)
@@ -126,18 +126,12 @@ where
         })
     }
 
-    let name = name.into_bytes_with_nul();
-    let desc = desc.into_bytes_with_nul();
-
-    let name_ptr = name.as_ref().as_ptr() as *mut _;
-    let desc_ptr = desc.as_ref().as_ptr() as *mut _;
-
     let ctxt = Box::into_raw(Box::new(command));
 
     unsafe {
         BNRegisterPluginCommand(
-            name_ptr,
-            desc_ptr,
+            name.as_cstr().as_ptr(),
+            desc.as_cstr().as_ptr(),
             Some(cb_action::<C>),
             Some(cb_valid::<C>),
             ctxt as *mut _,
@@ -196,7 +190,7 @@ where
 /// ```
 pub fn register_command_for_address<S, C>(name: S, desc: S, command: C)
 where
-    S: BnStrCompatible,
+    S: AsCStr,
     C: AddressCommand,
 {
     extern "C" fn cb_action<C>(ctxt: *mut c_void, view: *mut BNBinaryView, addr: u64)
@@ -227,18 +221,12 @@ where
         })
     }
 
-    let name = name.into_bytes_with_nul();
-    let desc = desc.into_bytes_with_nul();
-
-    let name_ptr = name.as_ref().as_ptr() as *mut _;
-    let desc_ptr = desc.as_ref().as_ptr() as *mut _;
-
     let ctxt = Box::into_raw(Box::new(command));
 
     unsafe {
         BNRegisterPluginCommandForAddress(
-            name_ptr,
-            desc_ptr,
+            name.as_cstr().as_ptr(),
+            desc.as_cstr().as_ptr(),
             Some(cb_action::<C>),
             Some(cb_valid::<C>),
             ctxt as *mut _,
@@ -298,7 +286,7 @@ where
 /// ```
 pub fn register_command_for_range<S, C>(name: S, desc: S, command: C)
 where
-    S: BnStrCompatible,
+    S: AsCStr,
     C: RangeCommand,
 {
     extern "C" fn cb_action<C>(ctxt: *mut c_void, view: *mut BNBinaryView, addr: u64, len: u64)
@@ -334,18 +322,12 @@ where
         })
     }
 
-    let name = name.into_bytes_with_nul();
-    let desc = desc.into_bytes_with_nul();
-
-    let name_ptr = name.as_ref().as_ptr() as *mut _;
-    let desc_ptr = desc.as_ref().as_ptr() as *mut _;
-
     let ctxt = Box::into_raw(Box::new(command));
 
     unsafe {
         BNRegisterPluginCommandForRange(
-            name_ptr,
-            desc_ptr,
+            name.as_cstr().as_ptr(),
+            desc.as_cstr().as_ptr(),
             Some(cb_action::<C>),
             Some(cb_valid::<C>),
             ctxt as *mut _,
@@ -405,7 +387,7 @@ where
 /// ```
 pub fn register_command_for_function<S, C>(name: S, desc: S, command: C)
 where
-    S: BnStrCompatible,
+    S: AsCStr,
     C: FunctionCommand,
 {
     extern "C" fn cb_action<C>(ctxt: *mut c_void, view: *mut BNBinaryView, func: *mut BNFunction)
@@ -446,18 +428,12 @@ where
         })
     }
 
-    let name = name.into_bytes_with_nul();
-    let desc = desc.into_bytes_with_nul();
-
-    let name_ptr = name.as_ref().as_ptr() as *mut _;
-    let desc_ptr = desc.as_ref().as_ptr() as *mut _;
-
     let ctxt = Box::into_raw(Box::new(command));
 
     unsafe {
         BNRegisterPluginCommandForFunction(
-            name_ptr,
-            desc_ptr,
+            name.as_cstr().as_ptr(),
+            desc.as_cstr().as_ptr(),
             Some(cb_action::<C>),
             Some(cb_valid::<C>),
             ctxt as *mut _,
diff --git a/rust/src/component.rs b/rust/src/component.rs
index 497c1f572..4e0fa90e1 100644
--- a/rust/src/component.rs
+++ b/rust/src/component.rs
@@ -1,9 +1,8 @@
 use crate::binary_view::{BinaryView, BinaryViewExt};
 use crate::function::Function;
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use crate::types::ComponentReferencedType;
-use std::ffi::c_char;
 use std::fmt::Debug;
 use std::ptr::NonNull;
 
@@ -38,29 +37,19 @@ impl ComponentBuilder {
     pub fn finalize(self) -> Ref<Component> {
         let result = match (&self.parent, &self.name) {
             (None, None) => unsafe { BNCreateComponent(self.view.handle) },
-            (None, Some(name)) => {
-                let name_raw = name.into_bytes_with_nul();
-                unsafe {
-                    BNCreateComponentWithName(self.view.handle, name_raw.as_ptr() as *mut c_char)
-                }
-            }
-            (Some(guid), None) => {
-                let guid_raw = guid.into_bytes_with_nul();
-                unsafe {
-                    BNCreateComponentWithParent(self.view.handle, guid_raw.as_ptr() as *mut c_char)
-                }
-            }
-            (Some(guid), Some(name)) => {
-                let guid_raw = guid.into_bytes_with_nul();
-                let name_raw = name.into_bytes_with_nul();
-                unsafe {
-                    BNCreateComponentWithParentAndName(
-                        self.view.handle,
-                        guid_raw.as_ptr() as *mut c_char,
-                        name_raw.as_ptr() as *mut c_char,
-                    )
-                }
-            }
+            (None, Some(name)) => unsafe {
+                BNCreateComponentWithName(self.view.handle, name.as_cstr().as_ptr())
+            },
+            (Some(guid), None) => unsafe {
+                BNCreateComponentWithParent(self.view.handle, guid.as_cstr().as_ptr())
+            },
+            (Some(guid), Some(name)) => unsafe {
+                BNCreateComponentWithParentAndName(
+                    self.view.handle,
+                    guid.as_cstr().as_ptr(),
+                    name.as_cstr().as_ptr(),
+                )
+            },
         };
         unsafe { Component::ref_from_raw(NonNull::new(result).unwrap()) }
     }
@@ -164,14 +153,8 @@ impl Component {
         unsafe { BnString::from_raw(result) }
     }
 
-    pub fn set_name<S: BnStrCompatible>(&self, name: S) {
-        let name = name.into_bytes_with_nul();
-        unsafe {
-            BNComponentSetName(
-                self.handle.as_ptr(),
-                name.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn set_name<S: AsCStr>(&self, name: S) {
+        unsafe { BNComponentSetName(self.handle.as_ptr(), name.as_cstr().as_ptr()) }
     }
 
     /// The component that contains this component, if it exists.
@@ -321,7 +304,7 @@ impl IntoComponentGuid for &Component {
     }
 }
 
-impl<S: BnStrCompatible> IntoComponentGuid for S {
+impl<S: AsCStr> IntoComponentGuid for S {
     fn component_guid(self) -> BnString {
         BnString::new(self)
     }
diff --git a/rust/src/custom_binary_view.rs b/rust/src/custom_binary_view.rs
index a43bbd4b5..fa9564154 100644
--- a/rust/src/custom_binary_view.rs
+++ b/rust/src/custom_binary_view.rs
@@ -41,7 +41,7 @@ use crate::Endianness;
 /// implementation of the `CustomBinaryViewType` must return.
 pub fn register_view_type<S, T, F>(name: S, long_name: S, constructor: F) -> &'static T
 where
-    S: BnStrCompatible,
+    S: AsCStr,
     T: CustomBinaryViewType,
     F: FnOnce(BinaryViewType) -> T,
 {
@@ -148,12 +148,6 @@ where
         })
     }
 
-    let name = name.into_bytes_with_nul();
-    let name_ptr = name.as_ref().as_ptr() as *mut _;
-
-    let long_name = long_name.into_bytes_with_nul();
-    let long_name_ptr = long_name.as_ref().as_ptr() as *mut _;
-
     let ctxt = Box::leak(Box::new(MaybeUninit::zeroed()));
 
     let mut bn_obj = BNCustomBinaryViewType {
@@ -167,7 +161,11 @@ where
     };
 
     unsafe {
-        let handle = BNRegisterBinaryViewType(name_ptr, long_name_ptr, &mut bn_obj as *mut _);
+        let handle = BNRegisterBinaryViewType(
+            name.as_cstr().as_ptr(),
+            long_name.as_cstr().as_ptr(),
+            &mut bn_obj as *mut _,
+        );
         if handle.is_null() {
             // avoid leaking the space allocated for the type, but also
             // avoid running its Drop impl (if any -- not that there should
@@ -359,9 +357,8 @@ impl BinaryViewType {
     }
 
     /// Looks up a BinaryViewType by its short name
-    pub fn by_name<N: BnStrCompatible>(name: N) -> Result<Self> {
-        let bytes = name.into_bytes_with_nul();
-        let handle = unsafe { BNGetBinaryViewTypeByName(bytes.as_ref().as_ptr() as *const _) };
+    pub fn by_name<N: AsCStr>(name: N) -> Result<Self> {
+        let handle = unsafe { BNGetBinaryViewTypeByName(name.as_cstr().as_ptr()) };
         match handle.is_null() {
             false => Ok(unsafe { BinaryViewType::from_raw(handle) }),
             true => Err(()),
diff --git a/rust/src/data_buffer.rs b/rust/src/data_buffer.rs
index 5b7d1ae61..bab6c5d94 100644
--- a/rust/src/data_buffer.rs
+++ b/rust/src/data_buffer.rs
@@ -121,7 +121,7 @@ impl DataBuffer {
     }
 
     pub fn from_escaped_string(value: &BnString) -> Self {
-        Self(unsafe { BNDecodeEscapedString(value.as_ptr()) })
+        Self(unsafe { BNDecodeEscapedString(value.as_c_str().as_ptr()) })
     }
 
     pub fn to_base64(&self) -> BnString {
@@ -129,7 +129,7 @@ impl DataBuffer {
     }
 
     pub fn from_base64(value: &BnString) -> Self {
-        Self(unsafe { BNDecodeBase64(value.as_ptr()) })
+        Self(unsafe { BNDecodeBase64(value.as_c_str().as_ptr()) })
     }
 
     pub fn zlib_compress(&self) -> Self {
diff --git a/rust/src/database.rs b/rust/src/database.rs
index 7174ebe8f..981f47fd7 100644
--- a/rust/src/database.rs
+++ b/rust/src/database.rs
@@ -4,7 +4,7 @@ pub mod undo;
 
 use binaryninjacore_sys::*;
 use std::collections::HashMap;
-use std::ffi::{c_char, c_void};
+use std::ffi::c_void;
 use std::fmt::Debug;
 use std::ptr::NonNull;
 
@@ -15,7 +15,7 @@ use crate::database::snapshot::{Snapshot, SnapshotId};
 use crate::file_metadata::FileMetadata;
 use crate::progress::{NoProgressCallback, ProgressCallback};
 use crate::rc::{Array, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 pub struct Database {
     pub(crate) handle: NonNull<BNDatabase>,
@@ -62,7 +62,7 @@ impl Database {
         unsafe { BNSetDatabaseCurrentSnapshot(self.handle.as_ptr(), id.0) }
     }
 
-    pub fn write_snapshot_data<N: BnStrCompatible>(
+    pub fn write_snapshot_data<N: AsCStr>(
         &self,
         parents: &[SnapshotId],
         file: &BinaryView,
@@ -90,12 +90,9 @@ impl Database {
         mut progress: P,
     ) -> SnapshotId
     where
-        N: BnStrCompatible,
+        N: AsCStr,
         P: ProgressCallback,
     {
-        let name_raw = name.into_bytes_with_nul();
-        let name_ptr = name_raw.as_ref().as_ptr() as *const c_char;
-
         let new_id = unsafe {
             BNWriteDatabaseSnapshotData(
                 self.handle.as_ptr(),
@@ -103,7 +100,7 @@ impl Database {
                 parents.as_ptr() as *mut _,
                 parents.len(),
                 file.handle,
-                name_ptr,
+                name.as_cstr().as_ptr(),
                 data.handle.as_ptr(),
                 auto_save,
                 &mut progress as *mut P as *mut c_void,
@@ -133,10 +130,8 @@ impl Database {
             Err(())
         }
     }
-    pub fn has_global<S: BnStrCompatible>(&self, key: S) -> bool {
-        let key_raw = key.into_bytes_with_nul();
-        let key_ptr = key_raw.as_ref().as_ptr() as *const c_char;
-        unsafe { BNDatabaseHasGlobal(self.handle.as_ptr(), key_ptr) != 0 }
+    pub fn has_global<S: AsCStr>(&self, key: S) -> bool {
+        unsafe { BNDatabaseHasGlobal(self.handle.as_ptr(), key.as_cstr().as_ptr()) != 0 }
     }
 
     /// Get a list of keys for all globals in the database
@@ -156,35 +151,34 @@ impl Database {
     }
 
     /// Get a specific global by key
-    pub fn read_global<S: BnStrCompatible>(&self, key: S) -> Option<BnString> {
-        let key_raw = key.into_bytes_with_nul();
-        let key_ptr = key_raw.as_ref().as_ptr() as *const c_char;
-        let result = unsafe { BNReadDatabaseGlobal(self.handle.as_ptr(), key_ptr) };
+    pub fn read_global<S: AsCStr>(&self, key: S) -> Option<BnString> {
+        let result = unsafe { BNReadDatabaseGlobal(self.handle.as_ptr(), key.as_cstr().as_ptr()) };
         unsafe { NonNull::new(result).map(|_| BnString::from_raw(result)) }
     }
 
     /// Write a global into the database
-    pub fn write_global<K: BnStrCompatible, V: BnStrCompatible>(&self, key: K, value: V) -> bool {
-        let key_raw = key.into_bytes_with_nul();
-        let key_ptr = key_raw.as_ref().as_ptr() as *const c_char;
-        let value_raw = value.into_bytes_with_nul();
-        let value_ptr = value_raw.as_ref().as_ptr() as *const c_char;
-        unsafe { BNWriteDatabaseGlobal(self.handle.as_ptr(), key_ptr, value_ptr) }
+    pub fn write_global<K: AsCStr, V: AsCStr>(&self, key: K, value: V) -> bool {
+        unsafe {
+            BNWriteDatabaseGlobal(
+                self.handle.as_ptr(),
+                key.as_cstr().as_ptr(),
+                value.as_cstr().as_ptr(),
+            )
+        }
     }
 
     /// Get a specific global by key, as a binary buffer
-    pub fn read_global_data<S: BnStrCompatible>(&self, key: S) -> Option<DataBuffer> {
-        let key_raw = key.into_bytes_with_nul();
-        let key_ptr = key_raw.as_ref().as_ptr() as *const c_char;
-        let result = unsafe { BNReadDatabaseGlobalData(self.handle.as_ptr(), key_ptr) };
+    pub fn read_global_data<S: AsCStr>(&self, key: S) -> Option<DataBuffer> {
+        let result =
+            unsafe { BNReadDatabaseGlobalData(self.handle.as_ptr(), key.as_cstr().as_ptr()) };
         NonNull::new(result).map(|_| DataBuffer::from_raw(result))
     }
 
     /// Write a binary buffer into a global in the database
-    pub fn write_global_data<K: BnStrCompatible>(&self, key: K, value: &DataBuffer) -> bool {
-        let key_raw = key.into_bytes_with_nul();
-        let key_ptr = key_raw.as_ref().as_ptr() as *const c_char;
-        unsafe { BNWriteDatabaseGlobalData(self.handle.as_ptr(), key_ptr, value.as_raw()) }
+    pub fn write_global_data<K: AsCStr>(&self, key: K, value: &DataBuffer) -> bool {
+        unsafe {
+            BNWriteDatabaseGlobalData(self.handle.as_ptr(), key.as_cstr().as_ptr(), value.as_raw())
+        }
     }
 
     /// Get the owning FileMetadata
diff --git a/rust/src/database/kvs.rs b/rust/src/database/kvs.rs
index 4b77bbdbf..ffe8c79bd 100644
--- a/rust/src/database/kvs.rs
+++ b/rust/src/database/kvs.rs
@@ -1,6 +1,6 @@
 use crate::data_buffer::DataBuffer;
 use crate::rc::{Array, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use binaryninjacore_sys::{
     BNBeginKeyValueStoreNamespace, BNEndKeyValueStoreNamespace, BNFreeKeyValueStore,
     BNGetKeyValueStoreBuffer, BNGetKeyValueStoreDataSize, BNGetKeyValueStoreKeys,
@@ -9,7 +9,6 @@ use binaryninjacore_sys::{
     BNNewKeyValueStoreReference, BNSetKeyValueStoreBuffer,
 };
 use std::collections::HashMap;
-use std::ffi::c_char;
 use std::fmt::Debug;
 use std::ptr::NonNull;
 
@@ -42,18 +41,17 @@ impl KeyValueStore {
     }
 
     /// Get the value for a single key
-    pub fn value<S: BnStrCompatible>(&self, key: S) -> Option<DataBuffer> {
-        let key_raw = key.into_bytes_with_nul();
-        let key_ptr = key_raw.as_ref().as_ptr() as *const c_char;
-        let result = unsafe { BNGetKeyValueStoreBuffer(self.handle.as_ptr(), key_ptr) };
+    pub fn value<S: AsCStr>(&self, key: S) -> Option<DataBuffer> {
+        let result =
+            unsafe { BNGetKeyValueStoreBuffer(self.handle.as_ptr(), key.as_cstr().as_ptr()) };
         NonNull::new(result).map(|_| DataBuffer::from_raw(result))
     }
 
     /// Set the value for a single key
-    pub fn set_value<S: BnStrCompatible>(&self, key: S, value: &DataBuffer) -> bool {
-        let key_raw = key.into_bytes_with_nul();
-        let key_ptr = key_raw.as_ref().as_ptr() as *const c_char;
-        unsafe { BNSetKeyValueStoreBuffer(self.handle.as_ptr(), key_ptr, value.as_raw()) }
+    pub fn set_value<S: AsCStr>(&self, key: S, value: &DataBuffer) -> bool {
+        unsafe {
+            BNSetKeyValueStoreBuffer(self.handle.as_ptr(), key.as_cstr().as_ptr(), value.as_raw())
+        }
     }
 
     /// Get the stored representation of the kvs
@@ -64,10 +62,8 @@ impl KeyValueStore {
     }
 
     /// Begin storing new keys into a namespace
-    pub fn begin_namespace<S: BnStrCompatible>(&self, name: S) {
-        let name_raw = name.into_bytes_with_nul();
-        let name_ptr = name_raw.as_ref().as_ptr() as *const c_char;
-        unsafe { BNBeginKeyValueStoreNamespace(self.handle.as_ptr(), name_ptr) }
+    pub fn begin_namespace<S: AsCStr>(&self, name: S) {
+        unsafe { BNBeginKeyValueStoreNamespace(self.handle.as_ptr(), name.as_cstr().as_ptr()) }
     }
 
     /// End storing new keys into a namespace
diff --git a/rust/src/database/snapshot.rs b/rust/src/database/snapshot.rs
index 35b0dc46d..a3b3aa5cf 100644
--- a/rust/src/database/snapshot.rs
+++ b/rust/src/database/snapshot.rs
@@ -4,7 +4,7 @@ use crate::database::undo::UndoEntry;
 use crate::database::Database;
 use crate::progress::ProgressCallback;
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use binaryninjacore_sys::{
     BNCollaborationFreeSnapshotIdList, BNFreeSnapshot, BNFreeSnapshotList, BNGetSnapshotChildren,
     BNGetSnapshotDatabase, BNGetSnapshotFileContents, BNGetSnapshotFileContentsHash,
@@ -14,7 +14,7 @@ use binaryninjacore_sys::{
     BNReadSnapshotDataWithProgress, BNSetSnapshotName, BNSnapshot, BNSnapshotHasAncestor,
     BNSnapshotHasContents, BNSnapshotHasUndo, BNSnapshotStoreData,
 };
-use std::ffi::{c_char, c_void};
+use std::ffi::c_void;
 use std::fmt;
 use std::fmt::{Debug, Display, Formatter};
 use std::ptr::NonNull;
@@ -50,10 +50,8 @@ impl Snapshot {
     }
 
     /// Set the displayed snapshot name
-    pub fn set_name<S: BnStrCompatible>(&self, value: S) {
-        let value_raw = value.into_bytes_with_nul();
-        let value_ptr = value_raw.as_ref().as_ptr() as *const c_char;
-        unsafe { BNSetSnapshotName(self.handle.as_ptr(), value_ptr) }
+    pub fn set_name<S: AsCStr>(&self, value: S) {
+        unsafe { BNSetSnapshotName(self.handle.as_ptr(), value.as_cstr().as_ptr()) }
     }
 
     /// If the snapshot was the result of an auto-save
diff --git a/rust/src/debuginfo.rs b/rust/src/debuginfo.rs
index 1353f8b17..01dc5e391 100644
--- a/rust/src/debuginfo.rs
+++ b/rust/src/debuginfo.rs
@@ -83,7 +83,7 @@ use crate::{
     binary_view::BinaryView,
     platform::Platform,
     rc::*,
-    string::{raw_to_string, BnStrCompatible, BnString},
+    string::{raw_to_string, AsCStr, BnString},
     types::{NameAndType, Type},
 };
 
@@ -115,9 +115,8 @@ impl DebugInfoParser {
     }
 
     /// Returns debug info parser of the given name, if it exists
-    pub fn from_name<S: BnStrCompatible>(name: S) -> Result<Ref<Self>, ()> {
-        let name = name.into_bytes_with_nul();
-        let parser = unsafe { BNGetDebugInfoParserByName(name.as_ref().as_ptr() as *mut _) };
+    pub fn from_name<S: AsCStr>(name: S) -> Result<Ref<Self>, ()> {
+        let parser = unsafe { BNGetDebugInfoParserByName(name.as_cstr().as_ptr()) };
 
         if parser.is_null() {
             Err(())
@@ -209,7 +208,7 @@ impl DebugInfoParser {
     // Registers a DebugInfoParser. See `binaryninja::debuginfo::DebugInfoParser` for more details.
     pub fn register<S, C>(name: S, parser_callbacks: C) -> Ref<Self>
     where
-        S: BnStrCompatible,
+        S: AsCStr,
         C: CustomDebugInfoParser,
     {
         extern "C" fn cb_is_valid<C>(ctxt: *mut c_void, view: *mut BNBinaryView) -> bool
@@ -259,13 +258,11 @@ impl DebugInfoParser {
             })
         }
 
-        let name = name.into_bytes_with_nul();
-        let name_ptr = name.as_ref().as_ptr() as *mut _;
         let ctxt = Box::into_raw(Box::new(parser_callbacks));
 
         unsafe {
             DebugInfoParser::from_raw(BNRegisterDebugInfoParser(
-                name_ptr,
+                name.as_cstr().as_ptr(),
                 Some(cb_is_valid::<C>),
                 Some(cb_parse_info::<C>),
                 ctxt as *mut _,
@@ -417,17 +414,10 @@ impl DebugInfo {
     }
 
     /// Returns all types within the parser
-    pub fn types_by_name<S: BnStrCompatible>(&self, parser_name: S) -> Vec<NameAndType> {
-        let parser_name = parser_name.into_bytes_with_nul();
-
+    pub fn types_by_name<S: AsCStr>(&self, parser_name: S) -> Vec<NameAndType> {
         let mut count: usize = 0;
-        let debug_types_ptr = unsafe {
-            BNGetDebugTypes(
-                self.handle,
-                parser_name.as_ref().as_ptr() as *mut _,
-                &mut count,
-            )
-        };
+        let debug_types_ptr =
+            unsafe { BNGetDebugTypes(self.handle, parser_name.as_cstr().as_ptr(), &mut count) };
         let result: Vec<_> = unsafe {
             std::slice::from_raw_parts_mut(debug_types_ptr, count)
                 .iter()
@@ -455,17 +445,10 @@ impl DebugInfo {
     }
 
     /// Returns all functions within the parser
-    pub fn functions_by_name<S: BnStrCompatible>(&self, parser_name: S) -> Vec<DebugFunctionInfo> {
-        let parser_name = parser_name.into_bytes_with_nul();
-
+    pub fn functions_by_name<S: AsCStr>(&self, parser_name: S) -> Vec<DebugFunctionInfo> {
         let mut count: usize = 0;
-        let functions_ptr = unsafe {
-            BNGetDebugFunctions(
-                self.handle,
-                parser_name.as_ref().as_ptr() as *mut _,
-                &mut count,
-            )
-        };
+        let functions_ptr =
+            unsafe { BNGetDebugFunctions(self.handle, parser_name.as_cstr().as_ptr(), &mut count) };
 
         let result: Vec<DebugFunctionInfo> = unsafe {
             std::slice::from_raw_parts_mut(functions_ptr, count)
@@ -495,19 +478,13 @@ impl DebugInfo {
     }
 
     /// Returns all data variables within the parser
-    pub fn data_variables_by_name<S: BnStrCompatible>(
+    pub fn data_variables_by_name<S: AsCStr>(
         &self,
         parser_name: S,
     ) -> Vec<NamedDataVariableWithType> {
-        let parser_name = parser_name.into_bytes_with_nul();
-
         let mut count: usize = 0;
         let data_variables_ptr = unsafe {
-            BNGetDebugDataVariables(
-                self.handle,
-                parser_name.as_ref().as_ptr() as *mut _,
-                &mut count,
-            )
+            BNGetDebugDataVariables(self.handle, parser_name.as_cstr().as_ptr(), &mut count)
         };
 
         let result: Vec<NamedDataVariableWithType> = unsafe {
@@ -537,15 +514,12 @@ impl DebugInfo {
         result
     }
 
-    pub fn type_by_name<S: BnStrCompatible>(&self, parser_name: S, name: S) -> Option<Ref<Type>> {
-        let parser_name = parser_name.into_bytes_with_nul();
-        let name = name.into_bytes_with_nul();
-
+    pub fn type_by_name<S: AsCStr>(&self, parser_name: S, name: S) -> Option<Ref<Type>> {
         let result = unsafe {
             BNGetDebugTypeByName(
                 self.handle,
-                parser_name.as_ref().as_ptr() as *mut _,
-                name.as_ref().as_ptr() as *mut _,
+                parser_name.as_cstr().as_ptr(),
+                name.as_cstr().as_ptr(),
             )
         };
         if !result.is_null() {
@@ -555,19 +529,17 @@ impl DebugInfo {
         }
     }
 
-    pub fn get_data_variable_by_name<S: BnStrCompatible>(
+    pub fn get_data_variable_by_name<S: AsCStr>(
         &self,
         parser_name: S,
         name: S,
     ) -> Option<NamedDataVariableWithType> {
-        let parser_name = parser_name.into_bytes_with_nul();
-        let name = name.into_bytes_with_nul();
         let mut dv = BNDataVariableAndName::default();
         unsafe {
             if BNGetDebugDataVariableByName(
                 self.handle,
-                parser_name.as_ref().as_ptr() as *mut _,
-                name.as_ref().as_ptr() as *mut _,
+                parser_name.as_cstr().as_ptr(),
+                name.as_cstr().as_ptr(),
                 &mut dv,
             ) {
                 Some(NamedDataVariableWithType::from_owned_raw(dv))
@@ -577,17 +549,16 @@ impl DebugInfo {
         }
     }
 
-    pub fn get_data_variable_by_address<S: BnStrCompatible>(
+    pub fn get_data_variable_by_address<S: AsCStr>(
         &self,
         parser_name: S,
         address: u64,
     ) -> Option<NamedDataVariableWithType> {
-        let parser_name = parser_name.into_bytes_with_nul();
         let mut dv = BNDataVariableAndName::default();
         unsafe {
             if BNGetDebugDataVariableByAddress(
                 self.handle,
-                parser_name.as_ref().as_ptr() as *mut _,
+                parser_name.as_cstr().as_ptr(),
                 address,
                 &mut dv,
             ) {
@@ -599,12 +570,10 @@ impl DebugInfo {
     }
 
     /// Returns a list of [`NameAndType`] where the `name` is the parser the type originates from.
-    pub fn get_types_by_name<S: BnStrCompatible>(&self, name: S) -> Vec<NameAndType> {
+    pub fn get_types_by_name<S: AsCStr>(&self, name: S) -> Vec<NameAndType> {
         let mut count: usize = 0;
-        let name = name.into_bytes_with_nul();
-        let raw_names_and_types_ptr = unsafe {
-            BNGetDebugTypesByName(self.handle, name.as_ref().as_ptr() as *mut _, &mut count)
-        };
+        let raw_names_and_types_ptr =
+            unsafe { BNGetDebugTypesByName(self.handle, name.as_cstr().as_ptr(), &mut count) };
 
         let raw_names_and_types: &[BNNameAndType] =
             unsafe { std::slice::from_raw_parts(raw_names_and_types_ptr, count) };
@@ -619,15 +588,10 @@ impl DebugInfo {
     }
 
     // The tuple is (DebugInfoParserName, address, type)
-    pub fn get_data_variables_by_name<S: BnStrCompatible>(
-        &self,
-        name: S,
-    ) -> Vec<(String, u64, Ref<Type>)> {
-        let name = name.into_bytes_with_nul();
-
+    pub fn get_data_variables_by_name<S: AsCStr>(&self, name: S) -> Vec<(String, u64, Ref<Type>)> {
         let mut count: usize = 0;
         let raw_variables_and_names = unsafe {
-            BNGetDebugDataVariablesByName(self.handle, name.as_ref().as_ptr() as *mut _, &mut count)
+            BNGetDebugDataVariablesByName(self.handle, name.as_cstr().as_ptr(), &mut count)
         };
 
         let variables_and_names: &[*mut BNDataVariableAndName] =
@@ -674,96 +638,53 @@ impl DebugInfo {
         result
     }
 
-    pub fn remove_parser_info<S: BnStrCompatible>(&self, parser_name: S) -> bool {
-        let parser_name = parser_name.into_bytes_with_nul();
-
-        unsafe { BNRemoveDebugParserInfo(self.handle, parser_name.as_ref().as_ptr() as *mut _) }
+    pub fn remove_parser_info<S: AsCStr>(&self, parser_name: S) -> bool {
+        unsafe { BNRemoveDebugParserInfo(self.handle, parser_name.as_cstr().as_ptr()) }
     }
 
-    pub fn remove_parser_types<S: BnStrCompatible>(&self, parser_name: S) -> bool {
-        let parser_name = parser_name.into_bytes_with_nul();
-
-        unsafe { BNRemoveDebugParserTypes(self.handle, parser_name.as_ref().as_ptr() as *mut _) }
+    pub fn remove_parser_types<S: AsCStr>(&self, parser_name: S) -> bool {
+        unsafe { BNRemoveDebugParserTypes(self.handle, parser_name.as_cstr().as_ptr()) }
     }
 
-    pub fn remove_parser_functions<S: BnStrCompatible>(&self, parser_name: S) -> bool {
-        let parser_name = parser_name.into_bytes_with_nul();
-
-        unsafe {
-            BNRemoveDebugParserFunctions(self.handle, parser_name.as_ref().as_ptr() as *mut _)
-        }
+    pub fn remove_parser_functions<S: AsCStr>(&self, parser_name: S) -> bool {
+        unsafe { BNRemoveDebugParserFunctions(self.handle, parser_name.as_cstr().as_ptr()) }
     }
 
-    pub fn remove_parser_data_variables<S: BnStrCompatible>(&self, parser_name: S) -> bool {
-        let parser_name = parser_name.into_bytes_with_nul();
-
-        unsafe {
-            BNRemoveDebugParserDataVariables(self.handle, parser_name.as_ref().as_ptr() as *mut _)
-        }
+    pub fn remove_parser_data_variables<S: AsCStr>(&self, parser_name: S) -> bool {
+        unsafe { BNRemoveDebugParserDataVariables(self.handle, parser_name.as_cstr().as_ptr()) }
     }
 
-    pub fn remove_type_by_name<S: BnStrCompatible>(&self, parser_name: S, name: S) -> bool {
-        let parser_name = parser_name.into_bytes_with_nul();
-        let name = name.into_bytes_with_nul();
-
+    pub fn remove_type_by_name<S: AsCStr>(&self, parser_name: S, name: S) -> bool {
         unsafe {
             BNRemoveDebugTypeByName(
                 self.handle,
-                parser_name.as_ref().as_ptr() as *mut _,
-                name.as_ref().as_ptr() as *mut _,
+                parser_name.as_cstr().as_ptr(),
+                name.as_cstr().as_ptr(),
             )
         }
     }
 
-    pub fn remove_function_by_index<S: BnStrCompatible>(
-        &self,
-        parser_name: S,
-        index: usize,
-    ) -> bool {
-        let parser_name = parser_name.into_bytes_with_nul();
-
-        unsafe {
-            BNRemoveDebugFunctionByIndex(
-                self.handle,
-                parser_name.as_ref().as_ptr() as *mut _,
-                index,
-            )
-        }
+    pub fn remove_function_by_index<S: AsCStr>(&self, parser_name: S, index: usize) -> bool {
+        unsafe { BNRemoveDebugFunctionByIndex(self.handle, parser_name.as_cstr().as_ptr(), index) }
     }
 
-    pub fn remove_data_variable_by_address<S: BnStrCompatible>(
-        &self,
-        parser_name: S,
-        address: u64,
-    ) -> bool {
-        let parser_name = parser_name.into_bytes_with_nul();
-
+    pub fn remove_data_variable_by_address<S: AsCStr>(&self, parser_name: S, address: u64) -> bool {
         unsafe {
-            BNRemoveDebugDataVariableByAddress(
-                self.handle,
-                parser_name.as_ref().as_ptr() as *mut _,
-                address,
-            )
+            BNRemoveDebugDataVariableByAddress(self.handle, parser_name.as_cstr().as_ptr(), address)
         }
     }
 
     /// Adds a type scoped under the current parser's name to the debug info
-    pub fn add_type<S: BnStrCompatible>(
-        &self,
-        name: S,
-        new_type: &Type,
-        components: &[&str],
-    ) -> bool {
-        // SAFETY: Lifetime of `components` will live long enough, so passing as_ptr is safe.
-        let raw_components: Vec<_> = components.iter().map(|&c| c.as_ptr()).collect();
+    pub fn add_type<S: AsCStr>(&self, name: S, new_type: &Type, components: &[&str]) -> bool {
+        let components = components.iter().map(|c| c.as_cstr()).collect::<Vec<_>>();
+        let mut raw_components = components.iter().map(|c| c.as_ptr()).collect::<Vec<_>>();
 
-        let name = name.into_bytes_with_nul();
         unsafe {
             BNAddDebugType(
                 self.handle,
-                name.as_ref().as_ptr() as *mut _,
+                name.as_cstr().as_ptr(),
                 new_type.handle,
-                raw_components.as_ptr() as *mut _,
+                raw_components.as_mut_ptr(),
                 components.len(),
             )
         }
@@ -771,59 +692,36 @@ impl DebugInfo {
 
     /// Adds a function scoped under the current parser's name to the debug info
     pub fn add_function(&self, new_func: &DebugFunctionInfo) -> bool {
-        let short_name_bytes = new_func
-            .short_name
-            .as_ref()
-            .map(|name| name.into_bytes_with_nul());
-        let short_name = short_name_bytes
-            .as_ref()
-            .map_or(std::ptr::null_mut() as *mut _, |name| name.as_ptr() as _);
-        let full_name_bytes = new_func
-            .full_name
-            .as_ref()
-            .map(|name| name.into_bytes_with_nul());
-        let full_name = full_name_bytes
-            .as_ref()
-            .map_or(std::ptr::null_mut() as *mut _, |name| name.as_ptr() as _);
-        let raw_name_bytes = new_func
-            .raw_name
-            .as_ref()
-            .map(|name| name.into_bytes_with_nul());
-        let raw_name = raw_name_bytes
-            .as_ref()
-            .map_or(std::ptr::null_mut() as *mut _, |name| name.as_ptr() as _);
-
-        let mut components_array: Vec<*mut ::std::os::raw::c_char> =
-            Vec::with_capacity(new_func.components.len());
-
-        let mut local_variables_array: Vec<BNVariableNameAndType> =
-            Vec::with_capacity(new_func.local_variables.len());
+        let short_name = new_func.short_name.as_ref().map(|name| name.as_cstr());
+        let full_name = new_func.full_name.as_ref().map(|name| name.as_cstr());
+        let raw_name = new_func.raw_name.as_ref().map(|name| name.as_cstr());
 
-        unsafe {
-            for component in &new_func.components {
-                components_array.push(BNAllocString(
-                    component.clone().into_bytes_with_nul().as_ptr() as _,
-                ));
-            }
+        let mut components = new_func
+            .components
+            .iter()
+            .map(|component| unsafe { BNAllocString(component.as_cstr().as_ptr()) })
+            .collect::<Vec<_>>();
 
-            for local_variable in &new_func.local_variables {
-                local_variables_array.push(BNVariableNameAndType {
-                    var: local_variable.variable.into(),
-                    autoDefined: local_variable.auto_defined,
-                    typeConfidence: local_variable.ty.confidence,
-                    name: BNAllocString(
-                        local_variable.name.clone().into_bytes_with_nul().as_ptr() as _
-                    ),
-                    type_: local_variable.ty.contents.handle,
-                });
-            }
+        let mut local_variables = new_func
+            .local_variables
+            .iter()
+            .map(|local_var| BNVariableNameAndType {
+                var: local_var.variable.into(),
+                autoDefined: local_var.auto_defined,
+                typeConfidence: local_var.ty.confidence,
+                name: unsafe { BNAllocString(local_var.name.as_cstr().as_ptr()) },
+                type_: local_var.ty.contents.handle,
+            })
+            .collect::<Vec<_>>();
 
+        let null_mut = std::ptr::null_mut();
+        unsafe {
             let result = BNAddDebugFunction(
                 self.handle,
                 &mut BNDebugFunctionInfo {
-                    shortName: short_name,
-                    fullName: full_name,
-                    rawName: raw_name,
+                    shortName: short_name.map_or(null_mut, |name| name.as_ptr().cast_mut()),
+                    fullName: full_name.map_or(null_mut, |name| name.as_ptr().cast_mut()),
+                    rawName: raw_name.map_or(null_mut, |name| name.as_ptr().cast_mut()),
                     address: new_func.address,
                     type_: match &new_func.type_ {
                         Some(type_) => type_.handle,
@@ -833,18 +731,18 @@ impl DebugInfo {
                         Some(platform) => platform.handle,
                         _ => std::ptr::null_mut(),
                     },
-                    components: components_array.as_ptr() as _,
+                    components: components.as_mut_ptr(),
                     componentN: new_func.components.len(),
-                    localVariables: local_variables_array.as_ptr() as _,
-                    localVariableN: local_variables_array.len(),
+                    localVariables: local_variables.as_mut_ptr(),
+                    localVariableN: local_variables.len(),
                 },
             );
 
-            for i in components_array {
+            for i in components {
                 BNFreeString(i);
             }
 
-            for i in &local_variables_array {
+            for i in &local_variables {
                 BNFreeString(i.name);
             }
             result
@@ -852,43 +750,26 @@ impl DebugInfo {
     }
 
     /// Adds a data variable scoped under the current parser's name to the debug info
-    pub fn add_data_variable<S: BnStrCompatible>(
+    pub fn add_data_variable<S: AsCStr>(
         &self,
         address: u64,
         t: &Type,
         name: Option<S>,
         components: &[&str],
     ) -> bool {
-        let mut components_array: Vec<*const ::std::os::raw::c_char> =
-            Vec::with_capacity(components.len());
-        for component in components {
-            components_array.push(component.as_ptr() as _);
-        }
+        let components = components.iter().map(|s| s.as_cstr()).collect::<Vec<_>>();
+        let mut components_array = components.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
+        let name_ptr = name.map_or(std::ptr::null(), |n| n.as_cstr().as_ptr());
 
-        match name {
-            Some(name) => {
-                let name = name.into_bytes_with_nul();
-                unsafe {
-                    BNAddDebugDataVariable(
-                        self.handle,
-                        address,
-                        t.handle,
-                        name.as_ref().as_ptr() as *mut _,
-                        components.as_ptr() as _,
-                        components.len(),
-                    )
-                }
-            }
-            None => unsafe {
-                BNAddDebugDataVariable(
-                    self.handle,
-                    address,
-                    t.handle,
-                    std::ptr::null_mut(),
-                    components.as_ptr() as _,
-                    components.len(),
-                )
-            },
+        unsafe {
+            BNAddDebugDataVariable(
+                self.handle,
+                address,
+                t.handle,
+                name_ptr,
+                components_array.as_mut_ptr(),
+                components_array.len(),
+            )
         }
     }
 
diff --git a/rust/src/demangle.rs b/rust/src/demangle.rs
index e4d0be1d9..0ce17a681 100644
--- a/rust/src/demangle.rs
+++ b/rust/src/demangle.rs
@@ -19,27 +19,25 @@ use std::ffi::{c_char, c_void};
 
 use crate::architecture::CoreArchitecture;
 use crate::binary_view::BinaryView;
-use crate::string::{raw_to_string, BnStrCompatible, BnString};
+use crate::string::{raw_to_string, AsCStr, BnString};
 use crate::types::{QualifiedName, Type};
 
 use crate::rc::*;
 
 pub type Result<R> = std::result::Result<R, ()>;
 
-pub fn demangle_generic<S: BnStrCompatible>(
+pub fn demangle_generic<S: AsCStr>(
     arch: &CoreArchitecture,
     mangled_name: S,
     view: Option<&BinaryView>,
     simplify: bool,
 ) -> Option<(QualifiedName, Option<Ref<Type>>)> {
-    let mangled_name_bwn = mangled_name.into_bytes_with_nul();
-    let mangled_name_ptr = mangled_name_bwn.as_ref();
     let mut out_type: *mut BNType = std::ptr::null_mut();
     let mut out_name = BNQualifiedName::default();
     let res = unsafe {
         BNDemangleGeneric(
             arch.handle,
-            mangled_name_ptr.as_ptr() as *const c_char,
+            mangled_name.as_cstr().as_ptr(),
             &mut out_type,
             &mut out_name,
             view.map(|v| v.handle).unwrap_or(std::ptr::null_mut()),
@@ -58,14 +56,12 @@ pub fn demangle_generic<S: BnStrCompatible>(
     }
 }
 
-pub fn demangle_llvm<S: BnStrCompatible>(mangled_name: S, simplify: bool) -> Option<QualifiedName> {
-    let mangled_name_bwn = mangled_name.into_bytes_with_nul();
-    let mangled_name_ptr = mangled_name_bwn.as_ref();
+pub fn demangle_llvm<S: AsCStr>(mangled_name: S, simplify: bool) -> Option<QualifiedName> {
     let mut out_name: *mut *mut std::os::raw::c_char = std::ptr::null_mut();
     let mut out_size: usize = 0;
     let res = unsafe {
         BNDemangleLLVM(
-            mangled_name_ptr.as_ptr() as *const c_char,
+            mangled_name.as_cstr().as_ptr(),
             &mut out_name,
             &mut out_size,
             simplify,
@@ -87,20 +83,18 @@ pub fn demangle_llvm<S: BnStrCompatible>(mangled_name: S, simplify: bool) -> Opt
     }
 }
 
-pub fn demangle_gnu3<S: BnStrCompatible>(
+pub fn demangle_gnu3<S: AsCStr>(
     arch: &CoreArchitecture,
     mangled_name: S,
     simplify: bool,
 ) -> Option<(QualifiedName, Option<Ref<Type>>)> {
-    let mangled_name_bwn = mangled_name.into_bytes_with_nul();
-    let mangled_name_ptr = mangled_name_bwn.as_ref();
     let mut out_type: *mut BNType = std::ptr::null_mut();
     let mut out_name: *mut *mut std::os::raw::c_char = std::ptr::null_mut();
     let mut out_size: usize = 0;
     let res = unsafe {
         BNDemangleGNU3(
             arch.handle,
-            mangled_name_ptr.as_ptr() as *const c_char,
+            mangled_name.as_cstr().as_ptr(),
             &mut out_type,
             &mut out_name,
             &mut out_size,
@@ -128,21 +122,18 @@ pub fn demangle_gnu3<S: BnStrCompatible>(
     }
 }
 
-pub fn demangle_ms<S: BnStrCompatible>(
+pub fn demangle_ms<S: AsCStr>(
     arch: &CoreArchitecture,
     mangled_name: S,
     simplify: bool,
 ) -> Option<(QualifiedName, Option<Ref<Type>>)> {
-    let mangled_name_bwn = mangled_name.into_bytes_with_nul();
-    let mangled_name_ptr = mangled_name_bwn.as_ref();
-
     let mut out_type: *mut BNType = std::ptr::null_mut();
     let mut out_name: *mut *mut std::os::raw::c_char = std::ptr::null_mut();
     let mut out_size: usize = 0;
     let res = unsafe {
         BNDemangleMS(
             arch.handle,
-            mangled_name_ptr.as_ptr() as *const c_char,
+            mangled_name.as_cstr().as_ptr(),
             &mut out_type,
             &mut out_name,
             &mut out_size,
@@ -187,19 +178,16 @@ impl Demangler {
         unsafe { Array::<Demangler>::new(demanglers, count, ()) }
     }
 
-    pub fn is_mangled_string<S: BnStrCompatible>(&self, name: S) -> bool {
-        let bytes = name.into_bytes_with_nul();
-        unsafe { BNIsDemanglerMangledName(self.handle, bytes.as_ref().as_ptr() as *const _) }
+    pub fn is_mangled_string<S: AsCStr>(&self, name: S) -> bool {
+        unsafe { BNIsDemanglerMangledName(self.handle, name.as_cstr().as_ptr()) }
     }
 
-    pub fn demangle<S: BnStrCompatible>(
+    pub fn demangle<S: AsCStr>(
         &self,
         arch: &CoreArchitecture,
         name: S,
         view: Option<&BinaryView>,
     ) -> Option<(QualifiedName, Option<Ref<Type>>)> {
-        let name_bytes = name.into_bytes_with_nul();
-
         let mut out_type = std::ptr::null_mut();
         let mut out_var_name = BNQualifiedName::default();
 
@@ -212,7 +200,7 @@ impl Demangler {
             BNDemanglerDemangle(
                 self.handle,
                 arch.handle,
-                name_bytes.as_ref().as_ptr() as *const _,
+                name.as_cstr().as_ptr(),
                 &mut out_type,
                 &mut out_var_name,
                 view_ptr,
@@ -236,9 +224,8 @@ impl Demangler {
         unsafe { BnString::from_raw(BNGetDemanglerName(self.handle)) }
     }
 
-    pub fn from_name<S: BnStrCompatible>(name: S) -> Option<Self> {
-        let name_bytes = name.into_bytes_with_nul();
-        let demangler = unsafe { BNGetDemanglerByName(name_bytes.as_ref().as_ptr() as *const _) };
+    pub fn from_name<S: AsCStr>(name: S) -> Option<Self> {
+        let demangler = unsafe { BNGetDemanglerByName(name.as_cstr().as_ptr()) };
         if demangler.is_null() {
             None
         } else {
@@ -248,7 +235,7 @@ impl Demangler {
 
     pub fn register<S, C>(name: S, demangler: C) -> Self
     where
-        S: BnStrCompatible,
+        S: AsCStr,
         C: CustomDemangler,
     {
         extern "C" fn cb_is_mangled_string<C>(ctxt: *mut c_void, name: *const c_char) -> bool
@@ -308,8 +295,6 @@ impl Demangler {
             })
         }
 
-        let name = name.into_bytes_with_nul();
-        let name_ptr = name.as_ref().as_ptr() as *mut _;
         let ctxt = Box::into_raw(Box::new(demangler));
 
         let callbacks = BNDemanglerCallbacks {
@@ -321,7 +306,7 @@ impl Demangler {
 
         unsafe {
             Demangler::from_raw(BNRegisterDemangler(
-                name_ptr,
+                name.as_cstr().as_ptr(),
                 Box::leak(Box::new(callbacks)),
             ))
         }
diff --git a/rust/src/disassembly.rs b/rust/src/disassembly.rs
index 9dfd160f4..abc10c906 100644
--- a/rust/src/disassembly.rs
+++ b/rust/src/disassembly.rs
@@ -22,7 +22,7 @@ use crate::function::{Location, NativeBlock};
 use crate::high_level_il as hlil;
 use crate::low_level_il as llil;
 use crate::medium_level_il as mlil;
-use crate::string::BnStrCompatible;
+use crate::string::AsCStr;
 use crate::string::{raw_to_string, strings_to_string_list, BnString};
 
 use crate::rc::*;
@@ -1247,7 +1247,7 @@ impl DisassemblyTextRenderer {
         unsafe { Array::new(tokens, count, ()) }
     }
 
-    pub fn wrap_comment<S1: BnStrCompatible, S2: BnStrCompatible, S3: BnStrCompatible>(
+    pub fn wrap_comment<S1: AsCStr, S2: AsCStr, S3: AsCStr>(
         &self,
         cur_line: DisassemblyTextLine,
         comment: S1,
@@ -1256,19 +1256,16 @@ impl DisassemblyTextRenderer {
         indent_spaces: S3,
     ) -> Array<DisassemblyTextLine> {
         let cur_line_raw = DisassemblyTextLine::into_raw(cur_line);
-        let comment_raw = comment.into_bytes_with_nul();
-        let leading_spaces_raw = leading_spaces.into_bytes_with_nul();
-        let indent_spaces_raw = indent_spaces.into_bytes_with_nul();
         let mut count = 0;
         let lines = unsafe {
             BNDisassemblyTextRendererWrapComment(
                 self.handle.as_ptr(),
                 &cur_line_raw,
                 &mut count,
-                comment_raw.as_ref().as_ptr() as *const ffi::c_char,
+                comment.as_cstr().as_ptr(),
                 has_auto_annotations,
-                leading_spaces_raw.as_ref().as_ptr() as *const ffi::c_char,
-                indent_spaces_raw.as_ref().as_ptr() as *const ffi::c_char,
+                leading_spaces.as_cstr().as_ptr(),
+                indent_spaces.as_cstr().as_ptr(),
             )
         };
         DisassemblyTextLine::free_raw(cur_line_raw);
diff --git a/rust/src/download_provider.rs b/rust/src/download_provider.rs
index 1e61c87de..6cc2a6a51 100644
--- a/rust/src/download_provider.rs
+++ b/rust/src/download_provider.rs
@@ -1,6 +1,6 @@
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
 use crate::settings::Settings;
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use binaryninjacore_sys::*;
 use std::collections::HashMap;
 use std::ffi::{c_void, CStr};
@@ -13,12 +13,8 @@ pub struct DownloadProvider {
 }
 
 impl DownloadProvider {
-    pub fn get<S: BnStrCompatible>(name: S) -> Option<DownloadProvider> {
-        let result = unsafe {
-            BNGetDownloadProviderByName(
-                name.into_bytes_with_nul().as_ref().as_ptr() as *const c_char
-            )
-        };
+    pub fn get<S: AsCStr>(name: S) -> Option<DownloadProvider> {
+        let result = unsafe { BNGetDownloadProviderByName(name.as_cstr().as_ptr()) };
         if result.is_null() {
             return None;
         }
@@ -130,7 +126,7 @@ impl DownloadInstance {
         }
     }
 
-    pub fn perform_request<S: BnStrCompatible>(
+    pub fn perform_request<S: AsCStr>(
         &mut self,
         url: S,
         callbacks: DownloadInstanceOutputCallbacks,
@@ -146,7 +142,7 @@ impl DownloadInstance {
         let result = unsafe {
             BNPerformDownloadRequest(
                 self.handle,
-                url.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
+                url.as_cstr().as_ptr(),
                 &mut cbs as *mut BNDownloadInstanceOutputCallbacks,
             )
         };
@@ -196,10 +192,10 @@ impl DownloadInstance {
     }
 
     pub fn perform_custom_request<
-        M: BnStrCompatible,
-        U: BnStrCompatible,
-        HK: BnStrCompatible,
-        HV: BnStrCompatible,
+        M: AsCStr,
+        U: AsCStr,
+        HK: AsCStr,
+        HV: AsCStr,
         I: IntoIterator<Item = (HK, HV)>,
     >(
         &mut self,
@@ -208,20 +204,13 @@ impl DownloadInstance {
         headers: I,
         callbacks: DownloadInstanceInputOutputCallbacks,
     ) -> Result<DownloadResponse, BnString> {
-        let mut header_keys = vec![];
-        let mut header_values = vec![];
-        for (key, value) in headers {
-            header_keys.push(key.into_bytes_with_nul());
-            header_values.push(value.into_bytes_with_nul());
-        }
+        let (keys, values): (Vec<_>, Vec<_>) = headers.into_iter().unzip();
 
-        let mut header_key_ptrs = vec![];
-        let mut header_value_ptrs = vec![];
+        let header_keys = keys.iter().map(|k| k.as_cstr()).collect::<Vec<_>>();
+        let header_values = values.iter().map(|v| v.as_cstr()).collect::<Vec<_>>();
 
-        for (key, value) in header_keys.iter().zip(header_values.iter()) {
-            header_key_ptrs.push(key.as_ref().as_ptr() as *const c_char);
-            header_value_ptrs.push(value.as_ref().as_ptr() as *const c_char);
-        }
+        let header_key_ptrs = header_keys.iter().map(|k| k.as_ptr()).collect::<Vec<_>>();
+        let header_value_ptrs = header_values.iter().map(|v| v.as_ptr()).collect::<Vec<_>>();
 
         let callbacks = Box::into_raw(Box::new(callbacks));
         let mut cbs = BNDownloadInstanceInputOutputCallbacks {
@@ -238,8 +227,8 @@ impl DownloadInstance {
         let result = unsafe {
             BNPerformCustomRequest(
                 self.handle,
-                method.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
-                url.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
+                method.as_cstr().as_ptr(),
+                url.as_cstr().as_ptr(),
                 header_key_ptrs.len() as u64,
                 header_key_ptrs.as_ptr(),
                 header_value_ptrs.as_ptr(),
diff --git a/rust/src/enterprise.rs b/rust/src/enterprise.rs
index 7030b194a..f97983648 100644
--- a/rust/src/enterprise.rs
+++ b/rust/src/enterprise.rs
@@ -1,5 +1,5 @@
 use crate::rc::Array;
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use std::ffi::c_void;
 use std::marker::PhantomData;
 use std::time::{Duration, SystemTime, UNIX_EPOCH};
@@ -107,13 +107,8 @@ pub fn server_url() -> BnString {
     unsafe { BnString::from_raw(binaryninjacore_sys::BNGetEnterpriseServerUrl()) }
 }
 
-pub fn set_server_url<S: BnStrCompatible>(url: S) -> Result<(), ()> {
-    let url = url.into_bytes_with_nul();
-    let result = unsafe {
-        binaryninjacore_sys::BNSetEnterpriseServerUrl(
-            url.as_ref().as_ptr() as *const std::os::raw::c_char
-        )
-    };
+pub fn set_server_url<S: AsCStr>(url: S) -> Result<(), ()> {
+    let result = unsafe { binaryninjacore_sys::BNSetEnterpriseServerUrl(url.as_cstr().as_ptr()) };
     if result {
         Ok(())
     } else {
@@ -166,25 +161,22 @@ pub fn is_server_license_still_activated() -> bool {
 
 pub fn authenticate_server_with_credentials<U, P>(username: U, password: P, remember: bool) -> bool
 where
-    U: BnStrCompatible,
-    P: BnStrCompatible,
+    U: AsCStr,
+    P: AsCStr,
 {
-    let username = username.into_bytes_with_nul();
-    let password = password.into_bytes_with_nul();
     unsafe {
         binaryninjacore_sys::BNAuthenticateEnterpriseServerWithCredentials(
-            username.as_ref().as_ptr() as *const std::os::raw::c_char,
-            password.as_ref().as_ptr() as *const std::os::raw::c_char,
+            username.as_cstr().as_ptr(),
+            password.as_cstr().as_ptr(),
             remember,
         )
     }
 }
 
-pub fn authenticate_server_with_method<S: BnStrCompatible>(method: S, remember: bool) -> bool {
-    let method = method.into_bytes_with_nul();
+pub fn authenticate_server_with_method<S: AsCStr>(method: S, remember: bool) -> bool {
     unsafe {
         binaryninjacore_sys::BNAuthenticateEnterpriseServerWithMethod(
-            method.as_ref().as_ptr() as *const std::os::raw::c_char,
+            method.as_cstr().as_ptr(),
             remember,
         )
     }
diff --git a/rust/src/external_library.rs b/rust/src/external_library.rs
index 2b3594b64..3318b1828 100644
--- a/rust/src/external_library.rs
+++ b/rust/src/external_library.rs
@@ -1,9 +1,8 @@
 use crate::project::file::ProjectFile;
 use crate::rc::{CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use crate::symbol::Symbol;
 use binaryninjacore_sys::*;
-use std::ffi::c_char;
 use std::fmt::Debug;
 use std::ptr::NonNull;
 
@@ -167,10 +166,8 @@ impl ExternalLocation {
 
     /// Set the symbol pointed to by this ExternalLocation.
     /// ExternalLocations must have a valid target address and/or symbol set.
-    pub fn set_target_symbol<S: BnStrCompatible>(&self, symbol: Option<S>) -> bool {
-        let symbol = symbol
-            .map(|x| x.into_bytes_with_nul().as_ref().as_ptr() as *const c_char)
-            .unwrap_or(std::ptr::null_mut());
+    pub fn set_target_symbol<S: AsCStr>(&self, symbol: Option<S>) -> bool {
+        let symbol = symbol.map_or(std::ptr::null(), |x| x.as_cstr().as_ptr());
         unsafe { BNExternalLocationSetTargetSymbol(self.handle.as_ptr(), symbol) }
     }
 }
diff --git a/rust/src/file_metadata.rs b/rust/src/file_metadata.rs
index f1923b407..59f13f3a3 100644
--- a/rust/src/file_metadata.rs
+++ b/rust/src/file_metadata.rs
@@ -52,7 +52,7 @@ impl FileMetadata {
         Self::ref_from_raw(unsafe { BNCreateFileMetadata() })
     }
 
-    pub fn with_filename<S: BnStrCompatible>(name: S) -> Ref<Self> {
+    pub fn with_filename<S: AsCStr>(name: S) -> Ref<Self> {
         let ret = FileMetadata::new();
         ret.set_filename(name);
         ret
@@ -75,12 +75,8 @@ impl FileMetadata {
         }
     }
 
-    pub fn set_filename<S: BnStrCompatible>(&self, name: S) {
-        let name = name.into_bytes_with_nul();
-
-        unsafe {
-            BNSetFilename(self.handle, name.as_ref().as_ptr() as *mut _);
-        }
+    pub fn set_filename<S: AsCStr>(&self, name: S) {
+        unsafe { BNSetFilename(self.handle, name.as_cstr().as_ptr()) }
     }
 
     pub fn modified(&self) -> bool {
@@ -107,10 +103,8 @@ impl FileMetadata {
         self.is_database_backed_for_view_type("")
     }
 
-    pub fn is_database_backed_for_view_type<S: BnStrCompatible>(&self, view_type: S) -> bool {
-        let view_type = view_type.into_bytes_with_nul();
-
-        unsafe { BNIsBackedByDatabase(self.handle, view_type.as_ref().as_ptr() as *const _) }
+    pub fn is_database_backed_for_view_type<S: AsCStr>(&self, view_type: S) -> bool {
+        unsafe { BNIsBackedByDatabase(self.handle, view_type.as_cstr().as_ptr()) }
     }
 
     pub fn run_undoable_transaction<F: FnOnce() -> Result<T, E>, T, E>(
@@ -135,18 +129,12 @@ impl FileMetadata {
         unsafe { BnString::from_raw(BNBeginUndoActions(self.handle, anonymous_allowed)) }
     }
 
-    pub fn commit_undo_actions<S: BnStrCompatible>(&self, id: S) {
-        let id = id.into_bytes_with_nul();
-        unsafe {
-            BNCommitUndoActions(self.handle, id.as_ref().as_ptr() as *const _);
-        }
+    pub fn commit_undo_actions<S: AsCStr>(&self, id: S) {
+        unsafe { BNCommitUndoActions(self.handle, id.as_cstr().as_ptr()) }
     }
 
-    pub fn revert_undo_actions<S: BnStrCompatible>(&self, id: S) {
-        let id = id.into_bytes_with_nul();
-        unsafe {
-            BNRevertUndoActions(self.handle, id.as_ref().as_ptr() as *const _);
-        }
+    pub fn revert_undo_actions<S: AsCStr>(&self, id: S) {
+        unsafe { BNRevertUndoActions(self.handle, id.as_cstr().as_ptr()) }
     }
 
     pub fn undo(&self) {
@@ -169,11 +157,9 @@ impl FileMetadata {
         unsafe { BNGetCurrentOffset(self.handle) }
     }
 
-    pub fn navigate_to<S: BnStrCompatible>(&self, view: S, offset: u64) -> Result<(), ()> {
-        let view = view.into_bytes_with_nul();
-
+    pub fn navigate_to<S: AsCStr>(&self, view: S, offset: u64) -> Result<(), ()> {
         unsafe {
-            if BNNavigate(self.handle, view.as_ref().as_ptr() as *const _, offset) {
+            if BNNavigate(self.handle, view.as_cstr().as_ptr(), offset) {
                 Ok(())
             } else {
                 Err(())
@@ -181,11 +167,9 @@ impl FileMetadata {
         }
     }
 
-    pub fn view_of_type<S: BnStrCompatible>(&self, view: S) -> Option<Ref<BinaryView>> {
-        let view = view.into_bytes_with_nul();
-
+    pub fn view_of_type<S: AsCStr>(&self, view: S) -> Option<Ref<BinaryView>> {
         unsafe {
-            let raw_view_ptr = BNGetFileViewOfType(self.handle, view.as_ref().as_ptr() as *const _);
+            let raw_view_ptr = BNGetFileViewOfType(self.handle, view.as_cstr().as_ptr());
             match raw_view_ptr.is_null() {
                 false => Some(BinaryView::ref_from_raw(raw_view_ptr)),
                 true => None,
@@ -215,18 +199,17 @@ impl FileMetadata {
             return false;
         };
 
-        let file_path = file_path.as_ref().into_bytes_with_nul();
         unsafe {
             BNCreateDatabase(
                 raw_view.handle,
-                file_path.as_ptr() as *mut _,
+                file_path.as_ref().as_cstr().as_ptr(),
                 ptr::null_mut(),
             )
         }
     }
 
     // TODO: Pass settings?
-    pub fn create_database_with_progress<S: BnStrCompatible, P: ProgressCallback>(
+    pub fn create_database_with_progress<P: ProgressCallback>(
         &self,
         file_path: impl AsRef<Path>,
         mut progress: P,
@@ -235,11 +218,10 @@ impl FileMetadata {
         let Some(raw_view) = self.view_of_type("Raw") else {
             return false;
         };
-        let file_path = file_path.as_ref().into_bytes_with_nul();
         unsafe {
             BNCreateDatabaseWithProgress(
                 raw_view.handle,
-                file_path.as_ptr() as *mut _,
+                file_path.as_ref().as_cstr().as_ptr(),
                 &mut progress as *mut P as *mut c_void,
                 Some(P::cb_progress_callback),
                 ptr::null_mut(),
@@ -256,14 +238,12 @@ impl FileMetadata {
         unsafe { BNSaveAutoSnapshot(raw_view.handle, ptr::null_mut() as *mut _) }
     }
 
-    pub fn open_database_for_configuration<S: BnStrCompatible>(
+    pub fn open_database_for_configuration<S: AsCStr>(
         &self,
         filename: S,
     ) -> Result<Ref<BinaryView>, ()> {
-        let filename = filename.into_bytes_with_nul();
         unsafe {
-            let bv =
-                BNOpenDatabaseForConfiguration(self.handle, filename.as_ref().as_ptr() as *const _);
+            let bv = BNOpenDatabaseForConfiguration(self.handle, filename.as_cstr().as_ptr());
 
             if bv.is_null() {
                 Err(())
@@ -273,11 +253,8 @@ impl FileMetadata {
         }
     }
 
-    pub fn open_database<S: BnStrCompatible>(&self, filename: S) -> Result<Ref<BinaryView>, ()> {
-        let filename = filename.into_bytes_with_nul();
-        let filename_ptr = filename.as_ref().as_ptr() as *mut _;
-
-        let view = unsafe { BNOpenExistingDatabase(self.handle, filename_ptr) };
+    pub fn open_database<S: AsCStr>(&self, filename: S) -> Result<Ref<BinaryView>, ()> {
+        let view = unsafe { BNOpenExistingDatabase(self.handle, filename.as_cstr().as_ptr()) };
 
         if view.is_null() {
             Err(())
@@ -286,18 +263,15 @@ impl FileMetadata {
         }
     }
 
-    pub fn open_database_with_progress<S: BnStrCompatible, P: ProgressCallback>(
+    pub fn open_database_with_progress<S: AsCStr, P: ProgressCallback>(
         &self,
         filename: S,
         mut progress: P,
     ) -> Result<Ref<BinaryView>, ()> {
-        let filename = filename.into_bytes_with_nul();
-        let filename_ptr = filename.as_ref().as_ptr() as *mut _;
-
         let view = unsafe {
             BNOpenExistingDatabaseWithProgress(
                 self.handle,
-                filename_ptr,
+                filename.as_cstr().as_ptr(),
                 &mut progress as *mut P as *mut c_void,
                 Some(P::cb_progress_callback),
             )
diff --git a/rust/src/function.rs b/rust/src/function.rs
index 1f22512c3..0c700b9dd 100644
--- a/rust/src/function.rs
+++ b/rust/src/function.rs
@@ -372,12 +372,8 @@ impl Function {
         unsafe { BnString::from_raw(BNGetFunctionComment(self.handle)) }
     }
 
-    pub fn set_comment<S: BnStrCompatible>(&self, comment: S) {
-        let raw = comment.into_bytes_with_nul();
-
-        unsafe {
-            BNSetFunctionComment(self.handle, raw.as_ref().as_ptr() as *mut _);
-        }
+    pub fn set_comment<S: AsCStr>(&self, comment: S) {
+        unsafe { BNSetFunctionComment(self.handle, comment.as_cstr().as_ptr()) }
     }
 
     pub fn set_can_return_auto<T: Into<Conf<bool>>>(&self, can_return: T) {
@@ -394,12 +390,8 @@ impl Function {
         unsafe { BnString::from_raw(BNGetCommentForAddress(self.handle, addr)) }
     }
 
-    pub fn set_comment_at<S: BnStrCompatible>(&self, addr: u64, comment: S) {
-        let raw = comment.into_bytes_with_nul();
-
-        unsafe {
-            BNSetCommentForAddress(self.handle, addr, raw.as_ref().as_ptr() as *mut _);
-        }
+    pub fn set_comment_at<S: AsCStr>(&self, addr: u64, comment: S) {
+        unsafe { BNSetCommentForAddress(self.handle, addr, comment.as_cstr().as_ptr()) }
     }
 
     /// All comments in the function
@@ -1101,7 +1093,7 @@ impl Function {
     /// let crash = bv.create_tag_type("Crashes", "🎯");
     /// fun.add_tag(&crash, "Nullpointer dereference", Some(0x1337), false, None);
     /// ```
-    pub fn add_tag<S: BnStrCompatible>(
+    pub fn add_tag<S: AsCStr>(
         &self,
         tag_type: &TagType,
         data: S,
@@ -1698,20 +1690,18 @@ impl Function {
     /// * `display_type` - Desired display type
     /// * `arch` - (optional) Architecture of the instruction or IL line containing the token
     /// * `enum_display_typeid` - (optional) Whenever passing EnumDisplayType to `display_type`, passing a type ID here will specify the Enumeration display type. Must be a valid type ID and resolve to an enumeration type.
-    pub fn set_int_display_type(
+    pub fn set_int_display_type<S: AsCStr>(
         &self,
         instr_addr: u64,
         value: u64,
         operand: usize,
         display_type: IntegerDisplayType,
         arch: Option<CoreArchitecture>,
-        enum_display_typeid: Option<impl BnStrCompatible>,
+        enum_display_typeid: Option<S>,
     ) {
         let arch = arch.unwrap_or_else(|| self.arch());
-        let enum_display_typeid = enum_display_typeid.map(BnStrCompatible::into_bytes_with_nul);
-        let enum_display_typeid_ptr = enum_display_typeid
-            .map(|x| x.as_ref().as_ptr() as *const c_char)
-            .unwrap_or(std::ptr::null());
+        let enum_display_typeid_ptr =
+            enum_display_typeid.map_or(std::ptr::null(), |x| x.as_cstr().as_ptr());
         unsafe {
             BNSetIntegerConstantDisplayType(
                 self.handle,
diff --git a/rust/src/high_level_il/operation.rs b/rust/src/high_level_il/operation.rs
index 340b85a75..3a1bbbed7 100644
--- a/rust/src/high_level_il/operation.rs
+++ b/rust/src/high_level_il/operation.rs
@@ -1,12 +1,11 @@
 use binaryninjacore_sys::*;
-use core::ffi;
 use std::fmt::{Debug, Formatter};
 
 use super::HighLevelILLiftedInstruction;
 use crate::architecture::CoreIntrinsic;
 use crate::function::Function;
 use crate::rc::Ref;
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use crate::variable::{ConstantData, SSAVariable, Variable};
 
 #[derive(Clone, PartialEq, Eq)]
@@ -20,14 +19,9 @@ impl GotoLabel {
         unsafe { BnString::from_raw(BNGetGotoLabelName(self.function.handle, self.target)) }
     }
 
-    fn set_name<S: BnStrCompatible>(&self, name: S) {
-        let raw = name.into_bytes_with_nul();
+    fn set_name<S: AsCStr>(&self, name: S) {
         unsafe {
-            BNSetUserGotoLabelName(
-                self.function.handle,
-                self.target,
-                raw.as_ref().as_ptr() as *const ffi::c_char,
-            )
+            BNSetUserGotoLabelName(self.function.handle, self.target, name.as_cstr().as_ptr())
         }
     }
 }
@@ -327,7 +321,7 @@ impl LiftedLabel {
         self.target.name()
     }
 
-    pub fn set_name<S: BnStrCompatible>(&self, name: S) {
+    pub fn set_name<S: AsCStr>(&self, name: S) {
         self.target.set_name(name)
     }
 }
diff --git a/rust/src/interaction.rs b/rust/src/interaction.rs
index 1546bc5ae..524a907e3 100644
--- a/rust/src/interaction.rs
+++ b/rust/src/interaction.rs
@@ -21,7 +21,7 @@ use std::path::PathBuf;
 
 use crate::binary_view::BinaryView;
 use crate::rc::Ref;
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 pub fn get_text_line_input(prompt: &str, title: &str) -> Option<String> {
     let mut value: *mut c_char = std::ptr::null_mut();
@@ -29,8 +29,8 @@ pub fn get_text_line_input(prompt: &str, title: &str) -> Option<String> {
     let result = unsafe {
         BNGetTextLineInput(
             &mut value,
-            prompt.into_bytes_with_nul().as_ptr() as *mut _,
-            title.into_bytes_with_nul().as_ptr() as *mut _,
+            prompt.as_cstr().as_ptr(),
+            title.as_cstr().as_ptr(),
         )
     };
     if !result {
@@ -46,8 +46,8 @@ pub fn get_integer_input(prompt: &str, title: &str) -> Option<i64> {
     let result = unsafe {
         BNGetIntegerInput(
             &mut value,
-            prompt.into_bytes_with_nul().as_ptr() as *mut _,
-            title.into_bytes_with_nul().as_ptr() as *mut _,
+            prompt.as_cstr().as_ptr(),
+            title.as_cstr().as_ptr(),
         )
     };
 
@@ -64,8 +64,8 @@ pub fn get_address_input(prompt: &str, title: &str) -> Option<u64> {
     let result = unsafe {
         BNGetAddressInput(
             &mut value,
-            prompt.into_bytes_with_nul().as_ptr() as *mut _,
-            title.into_bytes_with_nul().as_ptr() as *mut _,
+            prompt.as_cstr().as_ptr(),
+            title.as_cstr().as_ptr(),
             std::ptr::null_mut(),
             0,
         )
@@ -84,8 +84,8 @@ pub fn get_open_filename_input(prompt: &str, extension: &str) -> Option<PathBuf>
     let result = unsafe {
         BNGetOpenFileNameInput(
             &mut value,
-            prompt.into_bytes_with_nul().as_ptr() as *mut _,
-            extension.into_bytes_with_nul().as_ptr() as *mut _,
+            prompt.as_cstr().as_ptr(),
+            extension.as_cstr().as_ptr(),
         )
     };
     if !result {
@@ -106,9 +106,9 @@ pub fn get_save_filename_input(
     let result = unsafe {
         BNGetSaveFileNameInput(
             &mut value,
-            prompt.into_bytes_with_nul().as_ptr() as *mut _,
-            extension.into_bytes_with_nul().as_ptr() as *mut _,
-            default_name.into_bytes_with_nul().as_ptr() as *mut _,
+            prompt.as_cstr().as_ptr(),
+            extension.as_cstr().as_ptr(),
+            default_name.as_cstr().as_ptr(),
         )
     };
     if !result {
@@ -125,8 +125,8 @@ pub fn get_directory_name_input(prompt: &str, default_name: &str) -> Option<Path
     let result = unsafe {
         BNGetDirectoryNameInput(
             &mut value,
-            prompt.into_bytes_with_nul().as_ptr() as *mut _,
-            default_name.into_bytes_with_nul().as_ptr() as *mut _,
+            prompt.as_cstr().as_ptr(),
+            default_name.as_cstr().as_ptr(),
         )
     };
     if !result {
@@ -148,8 +148,8 @@ pub fn show_message_box(
 ) -> MessageBoxButtonResult {
     unsafe {
         BNShowMessageBox(
-            title.into_bytes_with_nul().as_ptr() as *mut _,
-            text.into_bytes_with_nul().as_ptr() as *mut _,
+            title.as_cstr().as_ptr(),
+            text.as_cstr().as_ptr(),
             buttons,
             icon,
         )
@@ -210,7 +210,7 @@ impl FormInputBuilder {
         let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
         result.type_ = BNFormInputFieldType::LabelFormField;
         result.hasDefault = false;
-        result.prompt = text.as_ref().as_ptr() as *const c_char;
+        result.prompt = text.as_ptr();
         self.fields.push(result);
 
         self.data.push(FormData::Label { _text: text });
@@ -233,10 +233,10 @@ impl FormInputBuilder {
 
         let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
         result.type_ = BNFormInputFieldType::TextLineFormField;
-        result.prompt = prompt.as_ref().as_ptr() as *const c_char;
+        result.prompt = prompt.as_ptr();
         result.hasDefault = default.is_some();
         if let Some(ref default) = default {
-            result.stringDefault = default.as_ref().as_ptr() as *const c_char;
+            result.stringDefault = default.as_ptr();
         }
         self.fields.push(result);
 
@@ -254,10 +254,10 @@ impl FormInputBuilder {
 
         let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
         result.type_ = BNFormInputFieldType::MultilineTextFormField;
-        result.prompt = prompt.as_ref().as_ptr() as *const c_char;
+        result.prompt = prompt.as_ptr();
         result.hasDefault = default.is_some();
         if let Some(ref default) = default {
-            result.stringDefault = default.as_ref().as_ptr() as *const c_char;
+            result.stringDefault = default.as_ptr();
         }
         self.fields.push(result);
 
@@ -274,7 +274,7 @@ impl FormInputBuilder {
 
         let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
         result.type_ = BNFormInputFieldType::IntegerFormField;
-        result.prompt = prompt.as_ref().as_ptr() as *const c_char;
+        result.prompt = prompt.as_ptr();
         result.hasDefault = default.is_some();
         if let Some(default) = default {
             result.intDefault = default;
@@ -297,7 +297,7 @@ impl FormInputBuilder {
 
         let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
         result.type_ = BNFormInputFieldType::AddressFormField;
-        result.prompt = prompt.as_ref().as_ptr() as *const c_char;
+        result.prompt = prompt.as_ptr();
         if let Some(view) = view {
             // the view is being moved into result, there is no need to clone
             // and drop is intentionally being avoided with `Ref::into_raw`
@@ -317,15 +317,12 @@ impl FormInputBuilder {
     /// Form Field: Prompt for a choice from provided options
     pub fn choice_field(mut self, prompt: &str, choices: &[&str], default: Option<usize>) -> Self {
         let prompt = BnString::new(prompt);
-        let choices: Vec<BnString> = choices.iter().map(|&s| BnString::new(s)).collect();
+        let choices: Vec<BnString> = choices.iter().map(BnString::new).collect();
 
         let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
         result.type_ = BNFormInputFieldType::ChoiceFormField;
-        result.prompt = prompt.as_ref().as_ptr() as *const c_char;
-        let mut raw_choices: Vec<*const c_char> = choices
-            .iter()
-            .map(|c| c.as_ref().as_ptr() as *const c_char)
-            .collect();
+        result.prompt = prompt.as_ptr();
+        let mut raw_choices: Vec<_> = choices.iter().map(|c| c.as_ptr()).collect();
         result.choices = raw_choices.as_mut_ptr();
         result.count = choices.len();
         result.hasDefault = default.is_some();
@@ -350,20 +347,16 @@ impl FormInputBuilder {
         default: Option<&str>,
     ) -> Self {
         let prompt = BnString::new(prompt);
-        let ext = if let Some(ext) = ext {
-            BnString::new(ext)
-        } else {
-            BnString::new("")
-        };
+        let ext = BnString::new(ext.unwrap_or_default());
         let default = default.map(BnString::new);
 
         let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
         result.type_ = BNFormInputFieldType::OpenFileNameFormField;
-        result.prompt = prompt.as_ref().as_ptr() as *const c_char;
-        result.ext = ext.as_ref().as_ptr() as *const c_char;
+        result.prompt = prompt.as_ptr();
+        result.ext = ext.as_ptr();
         result.hasDefault = default.is_some();
         if let Some(ref default) = default {
-            result.stringDefault = default.as_ref().as_ptr() as *const c_char;
+            result.stringDefault = default.as_ptr();
         }
         self.fields.push(result);
 
@@ -384,26 +377,18 @@ impl FormInputBuilder {
         default: Option<&str>,
     ) -> Self {
         let prompt = BnString::new(prompt);
-        let ext = if let Some(ext) = ext {
-            BnString::new(ext)
-        } else {
-            BnString::new("")
-        };
-        let default_name = if let Some(default_name) = default_name {
-            BnString::new(default_name)
-        } else {
-            BnString::new("")
-        };
+        let ext = BnString::new(ext.unwrap_or_default());
+        let default_name = BnString::new(default_name.unwrap_or_default());
         let default = default.map(BnString::new);
 
         let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
         result.type_ = BNFormInputFieldType::SaveFileNameFormField;
-        result.prompt = prompt.as_ref().as_ptr() as *const c_char;
-        result.ext = ext.as_ref().as_ptr() as *const c_char;
-        result.defaultName = default_name.as_ref().as_ptr() as *const c_char;
+        result.prompt = prompt.as_ptr();
+        result.ext = ext.as_ptr();
+        result.defaultName = default_name.as_ptr();
         result.hasDefault = default.is_some();
         if let Some(ref default) = default {
-            result.stringDefault = default.as_ref().as_ptr() as *const c_char;
+            result.stringDefault = default.as_ptr();
         }
         self.fields.push(result);
 
@@ -424,20 +409,16 @@ impl FormInputBuilder {
         default: Option<&str>,
     ) -> Self {
         let prompt = BnString::new(prompt);
-        let default_name = if let Some(default_name) = default_name {
-            BnString::new(default_name)
-        } else {
-            BnString::new("")
-        };
+        let default_name = BnString::new(default_name.unwrap_or_default());
         let default = default.map(BnString::new);
 
         let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() };
         result.type_ = BNFormInputFieldType::DirectoryNameFormField;
-        result.prompt = prompt.as_ref().as_ptr() as *const c_char;
-        result.defaultName = default_name.as_ref().as_ptr() as *const c_char;
+        result.prompt = prompt.as_ptr();
+        result.defaultName = default_name.as_ptr();
         result.hasDefault = default.is_some();
         if let Some(ref default) = default {
-            result.stringDefault = default.as_ref().as_ptr() as *const c_char;
+            result.stringDefault = default.as_ptr();
         }
         self.fields.push(result);
 
@@ -495,7 +476,7 @@ impl FormInputBuilder {
             BNGetFormInput(
                 self.fields.as_mut_ptr(),
                 self.fields.len(),
-                title.into_bytes_with_nul().as_ptr() as *const _,
+                title.as_cstr().as_ptr(),
             )
         } {
             let result = self
@@ -577,7 +558,7 @@ pub fn run_progress_dialog<F: Fn(Box<dyn Fn(usize, usize) -> Result<(), ()>>)>(
 
     if unsafe {
         BNRunProgressDialog(
-            title.into_bytes_with_nul().as_ptr() as *mut _,
+            title.as_cstr().as_ptr(),
             can_cancel,
             Some(cb_task::<F>),
             &mut ctxt as *mut _ as *mut c_void,
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index a3df0cd35..9e53d25cd 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -102,7 +102,7 @@ use std::cmp;
 use std::collections::HashMap;
 use std::ffi::{c_char, c_void, CStr};
 use std::path::{Path, PathBuf};
-use string::BnStrCompatible;
+use string::AsCStr;
 use string::BnString;
 use string::IntoJson;
 
@@ -128,13 +128,11 @@ pub fn load_with_progress<P: ProgressCallback>(
     file_path: impl AsRef<Path>,
     mut progress: P,
 ) -> Option<Ref<BinaryView>> {
-    let file_path = file_path.as_ref().into_bytes_with_nul();
-    let options = c"";
     let handle = unsafe {
         BNLoadFilename(
-            file_path.as_ptr() as *mut _,
+            file_path.as_ref().as_cstr().as_ptr(),
             true,
-            options.as_ptr() as *mut c_char,
+            c"".as_ptr(),
             Some(P::cb_progress_callback),
             &mut progress as *mut P as *mut c_void,
         )
@@ -193,25 +191,18 @@ where
     O: IntoJson,
     P: ProgressCallback,
 {
-    let file_path = file_path.as_ref().into_bytes_with_nul();
     let options_or_default = if let Some(opt) = options {
-        opt.get_json_string()
-            .ok()?
-            .into_bytes_with_nul()
-            .as_ref()
-            .to_vec()
+        BnString::new(opt.get_json_string().ok()?)
     } else {
         Metadata::new_of_type(MetadataType::KeyValueDataType)
             .get_json_string()
             .ok()?
-            .as_ref()
-            .to_vec()
     };
     let handle = unsafe {
         BNLoadFilename(
-            file_path.as_ptr() as *mut _,
+            file_path.as_ref().as_cstr().as_ptr(),
             update_analysis_and_wait,
-            options_or_default.as_ptr() as *mut c_char,
+            options_or_default.as_ptr(),
             Some(P::cb_progress_callback),
             &mut progress as *mut P as *mut c_void,
         )
@@ -247,23 +238,17 @@ where
     P: ProgressCallback,
 {
     let options_or_default = if let Some(opt) = options {
-        opt.get_json_string()
-            .ok()?
-            .into_bytes_with_nul()
-            .as_ref()
-            .to_vec()
+        BnString::new(opt.get_json_string().ok()?)
     } else {
         Metadata::new_of_type(MetadataType::KeyValueDataType)
             .get_json_string()
             .ok()?
-            .as_ref()
-            .to_vec()
     };
     let handle = unsafe {
         BNLoadBinaryView(
             bv.handle as *mut _,
             update_analysis_and_wait,
-            options_or_default.as_ptr() as *mut c_char,
+            options_or_default.as_ptr(),
             Some(P::cb_progress_callback),
             &mut progress as *mut P as *mut c_void,
         )
@@ -292,8 +277,7 @@ pub fn bundled_plugin_directory() -> Result<PathBuf, ()> {
 }
 
 pub fn set_bundled_plugin_directory(new_dir: impl AsRef<Path>) {
-    let new_dir = new_dir.as_ref().into_bytes_with_nul();
-    unsafe { BNSetBundledPluginDirectory(new_dir.as_ptr() as *const c_char) };
+    unsafe { BNSetBundledPluginDirectory(new_dir.as_ref().as_cstr().as_ptr()) };
 }
 
 pub fn user_directory() -> PathBuf {
@@ -334,9 +318,7 @@ pub fn save_last_run() {
 }
 
 pub fn path_relative_to_bundled_plugin_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
-    let path_raw = path.as_ref().into_bytes_with_nul();
-    let s: *mut c_char =
-        unsafe { BNGetPathRelativeToBundledPluginDirectory(path_raw.as_ptr() as *const c_char) };
+    let s = unsafe { BNGetPathRelativeToBundledPluginDirectory(path.as_ref().as_cstr().as_ptr()) };
     if s.is_null() {
         return Err(());
     }
@@ -344,9 +326,7 @@ pub fn path_relative_to_bundled_plugin_directory(path: impl AsRef<Path>) -> Resu
 }
 
 pub fn path_relative_to_user_plugin_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
-    let path_raw = path.as_ref().into_bytes_with_nul();
-    let s: *mut c_char =
-        unsafe { BNGetPathRelativeToUserPluginDirectory(path_raw.as_ptr() as *const c_char) };
+    let s = unsafe { BNGetPathRelativeToUserPluginDirectory(path.as_ref().as_cstr().as_ptr()) };
     if s.is_null() {
         return Err(());
     }
@@ -354,9 +334,7 @@ pub fn path_relative_to_user_plugin_directory(path: impl AsRef<Path>) -> Result<
 }
 
 pub fn path_relative_to_user_directory(path: impl AsRef<Path>) -> Result<PathBuf, ()> {
-    let path_raw = path.as_ref().into_bytes_with_nul();
-    let s: *mut c_char =
-        unsafe { BNGetPathRelativeToUserDirectory(path_raw.as_ptr() as *const c_char) };
+    let s = unsafe { BNGetPathRelativeToUserDirectory(path.as_ref().as_cstr().as_ptr()) };
     if s.is_null() {
         return Err(());
     }
@@ -479,9 +457,8 @@ impl VersionInfo {
         let _ = unsafe { BnString::from_raw(value.channel) };
     }
 
-    pub fn from_string<S: BnStrCompatible>(string: S) -> Self {
-        let string = string.into_bytes_with_nul();
-        let result = unsafe { BNParseVersionString(string.as_ref().as_ptr() as *const c_char) };
+    pub fn from_string<S: AsCStr>(string: S) -> Self {
+        let result = unsafe { BNParseVersionString(string.as_cstr().as_ptr()) };
         Self::from_owned_raw(result)
     }
 }
@@ -538,14 +515,12 @@ pub fn license_count() -> i32 {
 /// 1. Check the BN_LICENSE environment variable
 /// 2. Check the Binary Ninja user directory for license.dat
 #[cfg(not(feature = "demo"))]
-pub fn set_license<S: BnStrCompatible + Default>(license: Option<S>) {
-    let license = license.unwrap_or_default().into_bytes_with_nul();
-    let license_slice = license.as_ref();
-    unsafe { BNSetLicense(license_slice.as_ptr() as *const c_char) }
+pub fn set_license<S: AsCStr + Default>(license: Option<S>) {
+    unsafe { BNSetLicense(license.unwrap_or_default().as_cstr().as_ptr()) }
 }
 
 #[cfg(feature = "demo")]
-pub fn set_license<S: BnStrCompatible + Default>(_license: Option<S>) {}
+pub fn set_license<S: AsCStr + Default>(_license: Option<S>) {}
 
 pub fn product() -> BnString {
     unsafe { BnString::from_raw(BNGetProduct()) }
@@ -564,10 +539,8 @@ pub fn is_ui_enabled() -> bool {
     unsafe { BNIsUIEnabled() }
 }
 
-pub fn is_database<S: BnStrCompatible>(filename: S) -> bool {
-    let filename = filename.into_bytes_with_nul();
-    let filename_slice = filename.as_ref();
-    unsafe { BNIsDatabase(filename_slice.as_ptr() as *const c_char) }
+pub fn is_database<S: AsCStr>(filename: S) -> bool {
+    unsafe { BNIsDatabase(filename.as_cstr().as_ptr()) }
 }
 
 pub fn plugin_abi_version() -> u32 {
@@ -594,16 +567,12 @@ pub fn plugin_ui_abi_minimum_version() -> u32 {
     BN_MINIMUM_UI_ABI_VERSION
 }
 
-pub fn add_required_plugin_dependency<S: BnStrCompatible>(name: S) {
-    unsafe {
-        BNAddRequiredPluginDependency(name.into_bytes_with_nul().as_ref().as_ptr() as *const c_char)
-    };
+pub fn add_required_plugin_dependency<S: AsCStr>(name: S) {
+    unsafe { BNAddRequiredPluginDependency(name.as_cstr().as_ptr()) };
 }
 
-pub fn add_optional_plugin_dependency<S: BnStrCompatible>(name: S) {
-    unsafe {
-        BNAddOptionalPluginDependency(name.into_bytes_with_nul().as_ref().as_ptr() as *const c_char)
-    };
+pub fn add_optional_plugin_dependency<S: AsCStr>(name: S) {
+    unsafe { BNAddOptionalPluginDependency(name.as_cstr().as_ptr()) };
 }
 
 // Provide ABI version automatically so that the core can verify binary compatibility
diff --git a/rust/src/medium_level_il/function.rs b/rust/src/medium_level_il/function.rs
index 5cc0b58b5..873312268 100644
--- a/rust/src/medium_level_il/function.rs
+++ b/rust/src/medium_level_il/function.rs
@@ -1,5 +1,4 @@
 use binaryninjacore_sys::*;
-use std::ffi::c_char;
 use std::fmt::{Debug, Formatter};
 use std::hash::{Hash, Hasher};
 
@@ -11,7 +10,7 @@ use crate::disassembly::DisassemblySettings;
 use crate::flowgraph::FlowGraph;
 use crate::function::{Function, Location};
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref, RefCountable};
-use crate::string::BnStrCompatible;
+use crate::string::AsCStr;
 use crate::types::Type;
 use crate::variable::{PossibleValueSet, RegisterValue, SSAVariable, UserVariableValue, Variable};
 
@@ -122,20 +121,19 @@ impl MediumLevelILFunction {
         unsafe { Array::new(raw_instr_idxs, count, self.to_owned()) }
     }
 
-    pub fn create_user_stack_var<'a, S: BnStrCompatible, C: Into<Conf<&'a Type>>>(
+    pub fn create_user_stack_var<'a, S: AsCStr, C: Into<Conf<&'a Type>>>(
         self,
         offset: i64,
         var_type: C,
         name: S,
     ) {
         let mut owned_raw_var_ty = Conf::<&Type>::into_raw(var_type.into());
-        let name = name.into_bytes_with_nul();
         unsafe {
             BNCreateUserStackVariable(
                 self.function().handle,
                 offset,
                 &mut owned_raw_var_ty,
-                name.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
             )
         }
     }
@@ -144,7 +142,7 @@ impl MediumLevelILFunction {
         unsafe { BNDeleteUserStackVariable(self.function().handle, offset) }
     }
 
-    pub fn create_user_var<'a, S: BnStrCompatible, C: Into<Conf<&'a Type>>>(
+    pub fn create_user_var<'a, S: AsCStr, C: Into<Conf<&'a Type>>>(
         &self,
         var: &Variable,
         var_type: C,
@@ -153,13 +151,12 @@ impl MediumLevelILFunction {
     ) {
         let raw_var = BNVariable::from(var);
         let mut owned_raw_var_ty = Conf::<&Type>::into_raw(var_type.into());
-        let name = name.into_bytes_with_nul();
         unsafe {
             BNCreateUserVariable(
                 self.function().handle,
                 &raw_var,
                 &mut owned_raw_var_ty,
-                name.as_ref().as_ptr() as *const _,
+                name.as_cstr().as_ptr(),
                 ignore_disjoint_uses,
             )
         }
@@ -274,21 +271,19 @@ impl MediumLevelILFunction {
         Ok(())
     }
 
-    pub fn create_auto_stack_var<'a, T: Into<Conf<&'a Type>>, S: BnStrCompatible>(
+    pub fn create_auto_stack_var<'a, T: Into<Conf<&'a Type>>, S: AsCStr>(
         &self,
         offset: i64,
         var_type: T,
         name: S,
     ) {
         let mut owned_raw_var_ty = Conf::<&Type>::into_raw(var_type.into());
-        let name = name.into_bytes_with_nul();
-        let name_c_str = name.as_ref();
         unsafe {
             BNCreateAutoStackVariable(
                 self.function().handle,
                 offset,
                 &mut owned_raw_var_ty,
-                name_c_str.as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
             )
         }
     }
@@ -297,7 +292,7 @@ impl MediumLevelILFunction {
         unsafe { BNDeleteAutoStackVariable(self.function().handle, offset) }
     }
 
-    pub fn create_auto_var<'a, S: BnStrCompatible, C: Into<Conf<&'a Type>>>(
+    pub fn create_auto_var<'a, S: AsCStr, C: Into<Conf<&'a Type>>>(
         &self,
         var: &Variable,
         var_type: C,
@@ -306,14 +301,12 @@ impl MediumLevelILFunction {
     ) {
         let raw_var = BNVariable::from(var);
         let mut owned_raw_var_ty = Conf::<&Type>::into_raw(var_type.into());
-        let name = name.into_bytes_with_nul();
-        let name_c_str = name.as_ref();
         unsafe {
             BNCreateAutoVariable(
                 self.function().handle,
                 &raw_var,
                 &mut owned_raw_var_ty,
-                name_c_str.as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
                 ignore_disjoint_uses,
             )
         }
diff --git a/rust/src/metadata.rs b/rust/src/metadata.rs
index d18b63395..2646e413a 100644
--- a/rust/src/metadata.rs
+++ b/rust/src/metadata.rs
@@ -1,5 +1,5 @@
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString, IntoJson};
+use crate::string::{AsCStr, BnString, IntoJson};
 use binaryninjacore_sys::*;
 use std::collections::HashMap;
 use std::os::raw::c_char;
@@ -265,16 +265,12 @@ impl Metadata {
         Ok(Some(unsafe { Self::ref_from_raw(ptr) }))
     }
 
-    pub fn get<S: BnStrCompatible>(&self, key: S) -> Result<Option<Ref<Metadata>>, ()> {
+    pub fn get<S: AsCStr>(&self, key: S) -> Result<Option<Ref<Metadata>>, ()> {
         if self.get_type() != MetadataType::KeyValueDataType {
             return Err(());
         }
-        let ptr: *mut BNMetadata = unsafe {
-            BNMetadataGetForKey(
-                self.handle,
-                key.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
-            )
-        };
+        let ptr: *mut BNMetadata =
+            unsafe { BNMetadataGetForKey(self.handle, key.as_cstr().as_ptr()) };
         if ptr.is_null() {
             return Ok(None);
         }
@@ -289,18 +285,12 @@ impl Metadata {
         Ok(())
     }
 
-    pub fn insert<S: BnStrCompatible>(&self, key: S, value: &Metadata) -> Result<(), ()> {
+    pub fn insert<S: AsCStr>(&self, key: S, value: &Metadata) -> Result<(), ()> {
         if self.get_type() != MetadataType::KeyValueDataType {
             return Err(());
         }
 
-        unsafe {
-            BNMetadataSetValueForKey(
-                self.handle,
-                key.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
-                value.handle,
-            )
-        };
+        unsafe { BNMetadataSetValueForKey(self.handle, key.as_cstr().as_ptr(), value.handle) };
         Ok(())
     }
 
@@ -313,17 +303,12 @@ impl Metadata {
         Ok(())
     }
 
-    pub fn remove_key<S: BnStrCompatible>(&self, key: S) -> Result<(), ()> {
+    pub fn remove_key<S: AsCStr>(&self, key: S) -> Result<(), ()> {
         if self.get_type() != MetadataType::KeyValueDataType {
             return Err(());
         }
 
-        unsafe {
-            BNMetadataRemoveKey(
-                self.handle,
-                key.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
-            )
-        };
+        unsafe { BNMetadataRemoveKey(self.handle, key.as_cstr().as_ptr()) };
         Ok(())
     }
 }
@@ -394,21 +379,13 @@ impl From<f64> for Ref<Metadata> {
 
 impl From<String> for Ref<Metadata> {
     fn from(value: String) -> Self {
-        unsafe {
-            Metadata::ref_from_raw(BNCreateMetadataStringData(
-                value.into_bytes_with_nul().as_ptr() as *const c_char,
-            ))
-        }
+        unsafe { Metadata::ref_from_raw(BNCreateMetadataStringData(value.as_cstr().as_ptr())) }
     }
 }
 
 impl From<&str> for Ref<Metadata> {
     fn from(value: &str) -> Self {
-        unsafe {
-            Metadata::ref_from_raw(BNCreateMetadataStringData(
-                value.into_bytes_with_nul().as_ptr() as *const c_char,
-            ))
-        }
+        unsafe { Metadata::ref_from_raw(BNCreateMetadataStringData(value.as_cstr().as_ptr())) }
     }
 }
 
@@ -442,17 +419,14 @@ impl From<&Array<Metadata>> for Ref<Metadata> {
     }
 }
 
-impl<S: BnStrCompatible> From<HashMap<S, Ref<Metadata>>> for Ref<Metadata> {
+impl<S: AsCStr> From<HashMap<S, Ref<Metadata>>> for Ref<Metadata> {
     fn from(value: HashMap<S, Ref<Metadata>>) -> Self {
-        let data: Vec<(S::Result, Ref<Metadata>)> = value
-            .into_iter()
-            .map(|(k, v)| (k.into_bytes_with_nul(), v))
-            .collect();
-        let mut keys: Vec<*const c_char> = data
+        let data = value
             .iter()
-            .map(|(k, _)| k.as_ref().as_ptr() as *const c_char)
-            .collect();
-        let mut values: Vec<*mut BNMetadata> = data.iter().map(|(_, v)| v.handle).collect();
+            .map(|(k, v)| (k.as_cstr(), v))
+            .collect::<Vec<_>>();
+        let mut keys = data.iter().map(|(k, _)| k.as_ptr()).collect::<Vec<_>>();
+        let mut values = data.iter().map(|(_, v)| v.handle).collect::<Vec<_>>();
 
         unsafe {
             Metadata::ref_from_raw(BNCreateMetadataValueStore(
@@ -466,19 +440,16 @@ impl<S: BnStrCompatible> From<HashMap<S, Ref<Metadata>>> for Ref<Metadata> {
 
 impl<S, T> From<&[(S, T)]> for Ref<Metadata>
 where
-    S: BnStrCompatible + Copy,
+    S: AsCStr,
     for<'a> &'a T: Into<Ref<Metadata>>,
 {
     fn from(value: &[(S, T)]) -> Self {
-        let data: Vec<(S::Result, Ref<Metadata>)> = value
-            .iter()
-            .map(|(k, v)| (k.into_bytes_with_nul(), v.into()))
-            .collect();
-        let mut keys: Vec<*const c_char> = data
+        let data = value
             .iter()
-            .map(|(k, _)| k.as_ref().as_ptr() as *const c_char)
-            .collect();
-        let mut values: Vec<*mut BNMetadata> = data.iter().map(|(_, v)| v.handle).collect();
+            .map(|(k, v)| (k.as_cstr(), v.into()))
+            .collect::<Vec<_>>();
+        let mut keys = data.iter().map(|(k, _)| k.as_ptr()).collect::<Vec<_>>();
+        let mut values = data.iter().map(|(_, v)| v.handle).collect::<Vec<_>>();
 
         unsafe {
             Metadata::ref_from_raw(BNCreateMetadataValueStore(
@@ -492,7 +463,7 @@ where
 
 impl<S, T, const N: usize> From<[(S, T); N]> for Ref<Metadata>
 where
-    S: BnStrCompatible + Copy,
+    S: AsCStr,
     for<'a> &'a T: Into<Ref<Metadata>>,
 {
     fn from(value: [(S, T); N]) -> Self {
@@ -546,19 +517,13 @@ impl From<&Vec<f64>> for Ref<Metadata> {
     }
 }
 
-impl<S: BnStrCompatible> From<Vec<S>> for Ref<Metadata> {
+impl<S: AsCStr> From<Vec<S>> for Ref<Metadata> {
     fn from(value: Vec<S>) -> Self {
-        let mut refs = vec![];
-        for v in value {
-            refs.push(v.into_bytes_with_nul());
-        }
-        let mut pointers = vec![];
-        for r in &refs {
-            pointers.push(r.as_ref().as_ptr() as *const c_char);
-        }
+        let refs = value.iter().map(|v| v.as_cstr()).collect::<Vec<_>>();
+        let mut pointers = refs.iter().map(|r| r.as_ptr()).collect::<Vec<_>>();
         unsafe {
             Metadata::ref_from_raw(BNCreateMetadataStringListData(
-                pointers.as_ptr() as *mut *const c_char,
+                pointers.as_mut_ptr(),
                 pointers.len(),
             ))
         }
diff --git a/rust/src/platform.rs b/rust/src/platform.rs
index 72f614304..5ad0416fc 100644
--- a/rust/src/platform.rs
+++ b/rust/src/platform.rs
@@ -82,10 +82,9 @@ impl Platform {
         Ref::new(Self { handle })
     }
 
-    pub fn by_name<S: BnStrCompatible>(name: S) -> Option<Ref<Self>> {
-        let raw_name = name.into_bytes_with_nul();
+    pub fn by_name<S: AsCStr>(name: S) -> Option<Ref<Self>> {
         unsafe {
-            let res = BNGetPlatformByName(raw_name.as_ref().as_ptr() as *mut _);
+            let res = BNGetPlatformByName(name.as_cstr().as_ptr());
 
             if res.is_null() {
                 None
@@ -113,27 +112,20 @@ impl Platform {
         }
     }
 
-    pub fn list_by_os<S: BnStrCompatible>(name: S) -> Array<Platform> {
-        let raw_name = name.into_bytes_with_nul();
-
+    pub fn list_by_os<S: AsCStr>(name: S) -> Array<Platform> {
         unsafe {
             let mut count = 0;
-            let handles = BNGetPlatformListByOS(raw_name.as_ref().as_ptr() as *mut _, &mut count);
+            let handles = BNGetPlatformListByOS(name.as_cstr().as_ptr(), &mut count);
 
             Array::new(handles, count, ())
         }
     }
 
-    pub fn list_by_os_and_arch<S: BnStrCompatible>(
-        name: S,
-        arch: &CoreArchitecture,
-    ) -> Array<Platform> {
-        let raw_name = name.into_bytes_with_nul();
-
+    pub fn list_by_os_and_arch<S: AsCStr>(name: S, arch: &CoreArchitecture) -> Array<Platform> {
         unsafe {
             let mut count = 0;
             let handles = BNGetPlatformListByOSAndArchitecture(
-                raw_name.as_ref().as_ptr() as *mut _,
+                name.as_cstr().as_ptr(),
                 arch.handle,
                 &mut count,
             );
@@ -151,10 +143,9 @@ impl Platform {
         }
     }
 
-    pub fn new<A: Architecture, S: BnStrCompatible>(arch: &A, name: S) -> Ref<Self> {
-        let name = name.into_bytes_with_nul();
+    pub fn new<A: Architecture, S: AsCStr>(arch: &A, name: S) -> Ref<Self> {
         unsafe {
-            let handle = BNCreatePlatform(arch.as_ref().handle, name.as_ref().as_ptr() as *mut _);
+            let handle = BNCreatePlatform(arch.as_ref().handle, name.as_cstr().as_ptr());
             assert!(!handle.is_null());
             Ref::new(Self { handle })
         }
@@ -179,25 +170,18 @@ impl Platform {
         unsafe { TypeContainer::from_raw(type_container_ptr.unwrap()) }
     }
 
-    pub fn get_type_libraries_by_name<T: BnStrCompatible>(&self, name: T) -> Array<TypeLibrary> {
+    pub fn get_type_libraries_by_name<T: AsCStr>(&self, name: T) -> Array<TypeLibrary> {
         let mut count = 0;
-        let name = name.into_bytes_with_nul();
         let result = unsafe {
-            BNGetPlatformTypeLibrariesByName(
-                self.handle,
-                name.as_ref().as_ptr() as *mut _,
-                &mut count,
-            )
+            BNGetPlatformTypeLibrariesByName(self.handle, name.as_cstr().as_ptr(), &mut count)
         };
         assert!(!result.is_null());
         unsafe { Array::new(result, count, ()) }
     }
 
-    pub fn register_os<S: BnStrCompatible>(&self, os: S) {
-        let os = os.into_bytes_with_nul();
-
+    pub fn register_os<S: AsCStr>(&self, os: S) {
         unsafe {
-            BNRegisterPlatform(os.as_ref().as_ptr() as *mut _, self.handle);
+            BNRegisterPlatform(os.as_cstr().as_ptr(), self.handle);
         }
     }
 
@@ -278,15 +262,12 @@ impl Platform {
         file_name: &str,
         include_dirs: &[BnString],
     ) -> Result<BnString, TypeParserError> {
-        let source_cstr = BnString::new(source);
-        let file_name_cstr = BnString::new(file_name);
-
         let mut result = ptr::null_mut();
         let mut error_string = ptr::null_mut();
         let success = unsafe {
             BNPreprocessSource(
-                source_cstr.as_ptr(),
-                file_name_cstr.as_ptr(),
+                source.as_cstr().as_ptr(),
+                file_name.as_cstr().as_ptr(),
                 &mut result,
                 &mut error_string,
                 include_dirs.as_ptr() as *mut *const ffi::c_char,
@@ -317,22 +298,18 @@ impl Platform {
         include_dirs: &[BnString],
         auto_type_source: &str,
     ) -> Result<TypeParserResult, TypeParserError> {
-        let source_cstr = BnString::new(src);
-        let file_name_cstr = BnString::new(filename);
-        let auto_type_source = BnString::new(auto_type_source);
-
         let mut raw_result = BNTypeParserResult::default();
         let mut error_string = ptr::null_mut();
         let success = unsafe {
             BNParseTypesFromSource(
                 self.handle,
-                source_cstr.as_ptr(),
-                file_name_cstr.as_ptr(),
+                src.as_cstr().as_ptr(),
+                filename.as_cstr().as_ptr(),
                 &mut raw_result,
                 &mut error_string,
                 include_dirs.as_ptr() as *mut *const ffi::c_char,
                 include_dirs.len(),
-                auto_type_source.as_ptr(),
+                auto_type_source.as_cstr().as_ptr(),
             )
         };
 
@@ -360,20 +337,17 @@ impl Platform {
         include_dirs: &[BnString],
         auto_type_source: &str,
     ) -> Result<TypeParserResult, TypeParserError> {
-        let file_name_cstr = BnString::new(filename);
-        let auto_type_source = BnString::new(auto_type_source);
-
         let mut raw_result = BNTypeParserResult::default();
         let mut error_string = ptr::null_mut();
         let success = unsafe {
             BNParseTypesFromSourceFile(
                 self.handle,
-                file_name_cstr.as_ptr(),
+                filename.as_cstr().as_ptr(),
                 &mut raw_result,
                 &mut error_string,
                 include_dirs.as_ptr() as *mut *const ffi::c_char,
                 include_dirs.len(),
-                auto_type_source.as_ptr(),
+                auto_type_source.as_cstr().as_ptr(),
             )
         };
 
diff --git a/rust/src/project.rs b/rust/src/project.rs
index fad1e5ef2..c34950455 100644
--- a/rust/src/project.rs
+++ b/rust/src/project.rs
@@ -1,7 +1,7 @@
 pub mod file;
 pub mod folder;
 
-use std::ffi::{c_char, c_void};
+use std::ffi::c_void;
 use std::fmt::Debug;
 use std::ptr::{null_mut, NonNull};
 use std::time::{Duration, SystemTime, UNIX_EPOCH};
@@ -13,7 +13,7 @@ use crate::progress::{NoProgressCallback, ProgressCallback};
 use crate::project::file::ProjectFile;
 use crate::project::folder::ProjectFolder;
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 pub struct Project {
     pub(crate) handle: NonNull<BNProject>,
@@ -39,24 +39,16 @@ impl Project {
     ///
     /// * `path` - Path to the project directory (.bnpr)
     /// * `name` - Name of the new project
-    pub fn create<P: BnStrCompatible, S: BnStrCompatible>(path: P, name: S) -> Option<Ref<Self>> {
-        let path_raw = path.into_bytes_with_nul();
-        let name_raw = name.into_bytes_with_nul();
-        let handle = unsafe {
-            BNCreateProject(
-                path_raw.as_ref().as_ptr() as *const c_char,
-                name_raw.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    pub fn create<P: AsCStr, S: AsCStr>(path: P, name: S) -> Option<Ref<Self>> {
+        let handle = unsafe { BNCreateProject(path.as_cstr().as_ptr(), name.as_cstr().as_ptr()) };
         NonNull::new(handle).map(|h| unsafe { Self::ref_from_raw(h) })
     }
 
     /// Open an existing project
     ///
     /// * `path` - Path to the project directory (.bnpr) or project metadata file (.bnpm)
-    pub fn open_project<P: BnStrCompatible>(path: P) -> Option<Ref<Self>> {
-        let path_raw = path.into_bytes_with_nul();
-        let handle = unsafe { BNOpenProject(path_raw.as_ref().as_ptr() as *const c_char) };
+    pub fn open_project<P: AsCStr>(path: P) -> Option<Ref<Self>> {
+        let handle = unsafe { BNOpenProject(path.as_cstr().as_ptr()) };
         NonNull::new(handle).map(|h| unsafe { Self::ref_from_raw(h) })
     }
 
@@ -99,14 +91,8 @@ impl Project {
     }
 
     /// Set the name of the project
-    pub fn set_name<S: BnStrCompatible>(&self, value: S) {
-        let value = value.into_bytes_with_nul();
-        unsafe {
-            BNProjectSetName(
-                self.handle.as_ptr(),
-                value.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn set_name<S: AsCStr>(&self, value: S) {
+        unsafe { BNProjectSetName(self.handle.as_ptr(), value.as_cstr().as_ptr()) }
     }
 
     /// Get the description of the project
@@ -115,22 +101,14 @@ impl Project {
     }
 
     /// Set the description of the project
-    pub fn set_description<S: BnStrCompatible>(&self, value: S) {
-        let value = value.into_bytes_with_nul();
-        unsafe {
-            BNProjectSetDescription(
-                self.handle.as_ptr(),
-                value.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn set_description<S: AsCStr>(&self, value: S) {
+        unsafe { BNProjectSetDescription(self.handle.as_ptr(), value.as_cstr().as_ptr()) }
     }
 
     /// Retrieves metadata stored under a key from the project
-    pub fn query_metadata<S: BnStrCompatible>(&self, key: S) -> Ref<Metadata> {
-        let key = key.into_bytes_with_nul();
-        let result = unsafe {
-            BNProjectQueryMetadata(self.handle.as_ptr(), key.as_ref().as_ptr() as *const c_char)
-        };
+    pub fn query_metadata<S: AsCStr>(&self, key: S) -> Ref<Metadata> {
+        let result =
+            unsafe { BNProjectQueryMetadata(self.handle.as_ptr(), key.as_cstr().as_ptr()) };
         unsafe { Metadata::ref_from_raw(result) }
     }
 
@@ -138,26 +116,15 @@ impl Project {
     ///
     /// * `key` - Key under which to store the Metadata object
     /// * `value` - Object to store
-    pub fn store_metadata<S: BnStrCompatible>(&self, key: S, value: &Metadata) -> bool {
-        let key_raw = key.into_bytes_with_nul();
+    pub fn store_metadata<S: AsCStr>(&self, key: S, value: &Metadata) -> bool {
         unsafe {
-            BNProjectStoreMetadata(
-                self.handle.as_ptr(),
-                key_raw.as_ref().as_ptr() as *const c_char,
-                value.handle,
-            )
+            BNProjectStoreMetadata(self.handle.as_ptr(), key.as_cstr().as_ptr(), value.handle)
         }
     }
 
     /// Removes the metadata associated with this `key` from the project
-    pub fn remove_metadata<S: BnStrCompatible>(&self, key: S) {
-        let key_raw = key.into_bytes_with_nul();
-        unsafe {
-            BNProjectRemoveMetadata(
-                self.handle.as_ptr(),
-                key_raw.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn remove_metadata<S: AsCStr>(&self, key: S) {
+        unsafe { BNProjectRemoveMetadata(self.handle.as_ptr(), key.as_cstr().as_ptr()) }
     }
 
     pub fn push_folder(&self, file: &ProjectFolder) {
@@ -176,8 +143,8 @@ impl Project {
         description: D,
     ) -> Result<Ref<ProjectFolder>, ()>
     where
-        P: BnStrCompatible,
-        D: BnStrCompatible,
+        P: AsCStr,
+        D: AsCStr,
     {
         self.create_folder_from_path_with_progress(path, parent, description, NoProgressCallback)
     }
@@ -196,20 +163,18 @@ impl Project {
         mut progress: PC,
     ) -> Result<Ref<ProjectFolder>, ()>
     where
-        P: BnStrCompatible,
-        D: BnStrCompatible,
+        P: AsCStr,
+        D: AsCStr,
         PC: ProgressCallback,
     {
-        let path_raw = path.into_bytes_with_nul();
-        let description_raw = description.into_bytes_with_nul();
         let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
 
         unsafe {
             let result = BNProjectCreateFolderFromPath(
                 self.handle.as_ptr(),
-                path_raw.as_ref().as_ptr() as *const c_char,
+                path.as_cstr().as_ptr(),
                 parent_ptr,
-                description_raw.as_ref().as_ptr() as *const c_char,
+                description.as_cstr().as_ptr(),
                 &mut progress as *mut PC as *mut c_void,
                 Some(PC::cb_progress_callback),
             );
@@ -229,18 +194,16 @@ impl Project {
         description: D,
     ) -> Result<Ref<ProjectFolder>, ()>
     where
-        N: BnStrCompatible,
-        D: BnStrCompatible,
+        N: AsCStr,
+        D: AsCStr,
     {
-        let name_raw = name.into_bytes_with_nul();
-        let description_raw = description.into_bytes_with_nul();
         let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
         unsafe {
             let result = BNProjectCreateFolder(
                 self.handle.as_ptr(),
                 parent_ptr,
-                name_raw.as_ref().as_ptr() as *const c_char,
-                description_raw.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
+                description.as_cstr().as_ptr(),
             );
             Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
         }
@@ -260,21 +223,18 @@ impl Project {
         id: I,
     ) -> Result<Ref<ProjectFolder>, ()>
     where
-        N: BnStrCompatible,
-        D: BnStrCompatible,
-        I: BnStrCompatible,
+        N: AsCStr,
+        D: AsCStr,
+        I: AsCStr,
     {
-        let name_raw = name.into_bytes_with_nul();
-        let description_raw = description.into_bytes_with_nul();
         let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
-        let id_raw = id.into_bytes_with_nul();
         unsafe {
             let result = BNProjectCreateFolderUnsafe(
                 self.handle.as_ptr(),
                 parent_ptr,
-                name_raw.as_ref().as_ptr() as *const c_char,
-                description_raw.as_ref().as_ptr() as *const c_char,
-                id_raw.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
+                description.as_cstr().as_ptr(),
+                id.as_cstr().as_ptr(),
             );
             Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
         }
@@ -292,10 +252,8 @@ impl Project {
     }
 
     /// Retrieve a folder in the project by unique folder `id`
-    pub fn folder_by_id<S: BnStrCompatible>(&self, id: S) -> Option<Ref<ProjectFolder>> {
-        let id_raw = id.into_bytes_with_nul();
-        let id_ptr = id_raw.as_ref().as_ptr() as *const c_char;
-        let result = unsafe { BNProjectGetFolderById(self.handle.as_ptr(), id_ptr) };
+    pub fn folder_by_id<S: AsCStr>(&self, id: S) -> Option<Ref<ProjectFolder>> {
+        let result = unsafe { BNProjectGetFolderById(self.handle.as_ptr(), id.as_cstr().as_ptr()) };
         let handle = NonNull::new(result)?;
         Some(unsafe { ProjectFolder::ref_from_raw(handle) })
     }
@@ -350,9 +308,9 @@ impl Project {
         description: D,
     ) -> Result<Ref<ProjectFile>, ()>
     where
-        P: BnStrCompatible,
-        N: BnStrCompatible,
-        D: BnStrCompatible,
+        P: AsCStr,
+        N: AsCStr,
+        D: AsCStr,
     {
         self.create_file_from_path_with_progress(
             path,
@@ -379,23 +337,20 @@ impl Project {
         mut progress: PC,
     ) -> Result<Ref<ProjectFile>, ()>
     where
-        P: BnStrCompatible,
-        N: BnStrCompatible,
-        D: BnStrCompatible,
+        P: AsCStr,
+        N: AsCStr,
+        D: AsCStr,
         PC: ProgressCallback,
     {
-        let path_raw = path.into_bytes_with_nul();
-        let name_raw = name.into_bytes_with_nul();
-        let description_raw = description.into_bytes_with_nul();
         let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
 
         unsafe {
             let result = BNProjectCreateFileFromPath(
                 self.handle.as_ptr(),
-                path_raw.as_ref().as_ptr() as *const c_char,
+                path.as_cstr().as_ptr(),
                 folder_ptr,
-                name_raw.as_ref().as_ptr() as *const c_char,
-                description_raw.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
+                description.as_cstr().as_ptr(),
                 &mut progress as *mut PC as *mut c_void,
                 Some(PC::cb_progress_callback),
             );
@@ -421,10 +376,10 @@ impl Project {
         creation_time: SystemTime,
     ) -> Result<Ref<ProjectFile>, ()>
     where
-        P: BnStrCompatible,
-        N: BnStrCompatible,
-        D: BnStrCompatible,
-        I: BnStrCompatible,
+        P: AsCStr,
+        N: AsCStr,
+        D: AsCStr,
+        I: AsCStr,
     {
         self.create_file_from_path_unsafe_with_progress(
             path,
@@ -458,26 +413,22 @@ impl Project {
         mut progress: PC,
     ) -> Result<Ref<ProjectFile>, ()>
     where
-        P: BnStrCompatible,
-        N: BnStrCompatible,
-        D: BnStrCompatible,
-        I: BnStrCompatible,
+        P: AsCStr,
+        N: AsCStr,
+        D: AsCStr,
+        I: AsCStr,
         PC: ProgressCallback,
     {
-        let path_raw = path.into_bytes_with_nul();
-        let name_raw = name.into_bytes_with_nul();
-        let description_raw = description.into_bytes_with_nul();
-        let id_raw = id.into_bytes_with_nul();
         let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
 
         unsafe {
             let result = BNProjectCreateFileFromPathUnsafe(
                 self.handle.as_ptr(),
-                path_raw.as_ref().as_ptr() as *const c_char,
+                path.as_cstr().as_ptr(),
                 folder_ptr,
-                name_raw.as_ref().as_ptr() as *const c_char,
-                description_raw.as_ref().as_ptr() as *const c_char,
-                id_raw.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
+                description.as_cstr().as_ptr(),
+                id.as_cstr().as_ptr(),
                 systime_to_bntime(creation_time).unwrap(),
                 &mut progress as *mut PC as *mut c_void,
                 Some(PC::cb_progress_callback),
@@ -500,8 +451,8 @@ impl Project {
         description: D,
     ) -> Result<Ref<ProjectFile>, ()>
     where
-        N: BnStrCompatible,
-        D: BnStrCompatible,
+        N: AsCStr,
+        D: AsCStr,
     {
         self.create_file_with_progress(contents, folder, name, description, NoProgressCallback)
     }
@@ -522,12 +473,10 @@ impl Project {
         mut progress: P,
     ) -> Result<Ref<ProjectFile>, ()>
     where
-        N: BnStrCompatible,
-        D: BnStrCompatible,
+        N: AsCStr,
+        D: AsCStr,
         P: ProgressCallback,
     {
-        let name_raw = name.into_bytes_with_nul();
-        let description_raw = description.into_bytes_with_nul();
         let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
 
         unsafe {
@@ -536,8 +485,8 @@ impl Project {
                 contents.as_ptr(),
                 contents.len(),
                 folder_ptr,
-                name_raw.as_ref().as_ptr() as *const c_char,
-                description_raw.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
+                description.as_cstr().as_ptr(),
                 &mut progress as *mut P as *mut c_void,
                 Some(P::cb_progress_callback),
             );
@@ -563,9 +512,9 @@ impl Project {
         creation_time: SystemTime,
     ) -> Result<Ref<ProjectFile>, ()>
     where
-        N: BnStrCompatible,
-        D: BnStrCompatible,
-        I: BnStrCompatible,
+        N: AsCStr,
+        D: AsCStr,
+        I: AsCStr,
     {
         self.create_file_unsafe_with_progress(
             contents,
@@ -599,14 +548,11 @@ impl Project {
         mut progress: P,
     ) -> Result<Ref<ProjectFile>, ()>
     where
-        N: BnStrCompatible,
-        D: BnStrCompatible,
-        I: BnStrCompatible,
+        N: AsCStr,
+        D: AsCStr,
+        I: AsCStr,
         P: ProgressCallback,
     {
-        let name_raw = name.into_bytes_with_nul();
-        let description_raw = description.into_bytes_with_nul();
-        let id_raw = id.into_bytes_with_nul();
         let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
 
         unsafe {
@@ -615,9 +561,9 @@ impl Project {
                 contents.as_ptr(),
                 contents.len(),
                 folder_ptr,
-                name_raw.as_ref().as_ptr() as *const c_char,
-                description_raw.as_ref().as_ptr() as *const c_char,
-                id_raw.as_ref().as_ptr() as *const c_char,
+                name.as_cstr().as_ptr(),
+                description.as_cstr().as_ptr(),
+                id.as_cstr().as_ptr(),
                 systime_to_bntime(creation_time).unwrap(),
                 &mut progress as *mut P as *mut c_void,
                 Some(P::cb_progress_callback),
@@ -635,21 +581,16 @@ impl Project {
     }
 
     /// Retrieve a file in the project by unique `id`
-    pub fn file_by_id<S: BnStrCompatible>(&self, id: S) -> Option<Ref<ProjectFile>> {
-        let id_raw = id.into_bytes_with_nul();
-        let id_ptr = id_raw.as_ref().as_ptr() as *const c_char;
-
-        let result = unsafe { BNProjectGetFileById(self.handle.as_ptr(), id_ptr) };
+    pub fn file_by_id<S: AsCStr>(&self, id: S) -> Option<Ref<ProjectFile>> {
+        let result = unsafe { BNProjectGetFileById(self.handle.as_ptr(), id.as_cstr().as_ptr()) };
         let handle = NonNull::new(result)?;
         Some(unsafe { ProjectFile::ref_from_raw(handle) })
     }
 
     /// Retrieve a file in the project by the `path` on disk
-    pub fn file_by_path<S: BnStrCompatible>(&self, path: S) -> Option<Ref<ProjectFile>> {
-        let path_raw = path.into_bytes_with_nul();
-        let path_ptr = path_raw.as_ref().as_ptr() as *const c_char;
-
-        let result = unsafe { BNProjectGetFileByPathOnDisk(self.handle.as_ptr(), path_ptr) };
+    pub fn file_by_path<S: AsCStr>(&self, path: S) -> Option<Ref<ProjectFile>> {
+        let result =
+            unsafe { BNProjectGetFileByPathOnDisk(self.handle.as_ptr(), path.as_cstr().as_ptr()) };
         let handle = NonNull::new(result)?;
         Some(unsafe { ProjectFile::ref_from_raw(handle) })
     }
diff --git a/rust/src/project/file.rs b/rust/src/project/file.rs
index 5071dd50b..464c27c1e 100644
--- a/rust/src/project/file.rs
+++ b/rust/src/project/file.rs
@@ -1,6 +1,6 @@
 use crate::project::{systime_from_bntime, Project, ProjectFolder};
 use crate::rc::{CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use binaryninjacore_sys::{
     BNFreeProjectFile, BNFreeProjectFileList, BNNewProjectFileReference, BNProjectFile,
     BNProjectFileExistsOnDisk, BNProjectFileExport, BNProjectFileGetCreationTimestamp,
@@ -8,7 +8,6 @@ use binaryninjacore_sys::{
     BNProjectFileGetPathOnDisk, BNProjectFileGetProject, BNProjectFileSetDescription,
     BNProjectFileSetFolder, BNProjectFileSetName,
 };
-use std::ffi::c_char;
 use std::fmt::Debug;
 use std::ptr::{null_mut, NonNull};
 use std::time::SystemTime;
@@ -57,14 +56,8 @@ impl ProjectFile {
     }
 
     /// Set the name of this file
-    pub fn set_name<S: BnStrCompatible>(&self, value: S) -> bool {
-        let value_raw = value.into_bytes_with_nul();
-        unsafe {
-            BNProjectFileSetName(
-                self.handle.as_ptr(),
-                value_raw.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn set_name<S: AsCStr>(&self, value: S) -> bool {
+        unsafe { BNProjectFileSetName(self.handle.as_ptr(), value.as_cstr().as_ptr()) }
     }
 
     /// Get the description of this file
@@ -73,14 +66,8 @@ impl ProjectFile {
     }
 
     /// Set the description of this file
-    pub fn set_description<S: BnStrCompatible>(&self, value: S) -> bool {
-        let value_raw = value.into_bytes_with_nul();
-        unsafe {
-            BNProjectFileSetDescription(
-                self.handle.as_ptr(),
-                value_raw.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn set_description<S: AsCStr>(&self, value: S) -> bool {
+        unsafe { BNProjectFileSetDescription(self.handle.as_ptr(), value.as_cstr().as_ptr()) }
     }
 
     /// Get the file creation time
@@ -104,14 +91,8 @@ impl ProjectFile {
     /// Export this file to disk, `true' if the export succeeded
     ///
     /// * `dest` - Destination path for the exported contents
-    pub fn export<S: BnStrCompatible>(&self, dest: S) -> bool {
-        let dest_raw = dest.into_bytes_with_nul();
-        unsafe {
-            BNProjectFileExport(
-                self.handle.as_ptr(),
-                dest_raw.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn export<S: AsCStr>(&self, dest: S) -> bool {
+        unsafe { BNProjectFileExport(self.handle.as_ptr(), dest.as_cstr().as_ptr()) }
     }
 }
 
diff --git a/rust/src/project/folder.rs b/rust/src/project/folder.rs
index 90b40051b..9282737bf 100644
--- a/rust/src/project/folder.rs
+++ b/rust/src/project/folder.rs
@@ -1,14 +1,14 @@
 use crate::progress::{NoProgressCallback, ProgressCallback};
 use crate::project::Project;
 use crate::rc::{CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use binaryninjacore_sys::{
     BNFreeProjectFolder, BNFreeProjectFolderList, BNNewProjectFolderReference, BNProjectFolder,
     BNProjectFolderExport, BNProjectFolderGetDescription, BNProjectFolderGetId,
     BNProjectFolderGetName, BNProjectFolderGetParent, BNProjectFolderGetProject,
     BNProjectFolderSetDescription, BNProjectFolderSetName, BNProjectFolderSetParent,
 };
-use std::ffi::{c_char, c_void};
+use std::ffi::c_void;
 use std::fmt::Debug;
 use std::ptr::{null_mut, NonNull};
 
@@ -46,14 +46,8 @@ impl ProjectFolder {
     }
 
     /// Set the name of this folder
-    pub fn set_name<S: BnStrCompatible>(&self, value: S) -> bool {
-        let value_raw = value.into_bytes_with_nul();
-        unsafe {
-            BNProjectFolderSetName(
-                self.handle.as_ptr(),
-                value_raw.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn set_name<S: AsCStr>(&self, value: S) -> bool {
+        unsafe { BNProjectFolderSetName(self.handle.as_ptr(), value.as_cstr().as_ptr()) }
     }
 
     /// Get the description of this folder
@@ -62,14 +56,8 @@ impl ProjectFolder {
     }
 
     /// Set the description of this folder
-    pub fn set_description<S: BnStrCompatible>(&self, value: S) -> bool {
-        let value_raw = value.into_bytes_with_nul();
-        unsafe {
-            BNProjectFolderSetDescription(
-                self.handle.as_ptr(),
-                value_raw.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn set_description<S: AsCStr>(&self, value: S) -> bool {
+        unsafe { BNProjectFolderSetDescription(self.handle.as_ptr(), value.as_cstr().as_ptr()) }
     }
 
     /// Get the folder that contains this folder
@@ -88,7 +76,7 @@ impl ProjectFolder {
     /// Recursively export this folder to disk, returns `true' if the export succeeded
     ///
     /// * `dest` - Destination path for the exported contents
-    pub fn export<S: BnStrCompatible>(&self, dest: S) -> bool {
+    pub fn export<S: AsCStr>(&self, dest: S) -> bool {
         self.export_with_progress(dest, NoProgressCallback)
     }
 
@@ -99,15 +87,13 @@ impl ProjectFolder {
     /// * `progress` - [`ProgressCallback`] that will be called as contents are exporting
     pub fn export_with_progress<S, P>(&self, dest: S, mut progress: P) -> bool
     where
-        S: BnStrCompatible,
+        S: AsCStr,
         P: ProgressCallback,
     {
-        let dest_raw = dest.into_bytes_with_nul();
-
         let success = unsafe {
             BNProjectFolderExport(
                 self.handle.as_ptr(),
-                dest_raw.as_ref().as_ptr() as *const c_char,
+                dest.as_cstr().as_ptr(),
                 &mut progress as *mut P as *mut c_void,
                 Some(P::cb_progress_callback),
             )
diff --git a/rust/src/relocation.rs b/rust/src/relocation.rs
index 8fa9a86b0..9e76e0686 100644
--- a/rust/src/relocation.rs
+++ b/rust/src/relocation.rs
@@ -1,6 +1,6 @@
 use crate::low_level_il::RegularLowLevelILFunction;
 use crate::rc::Guard;
-use crate::string::BnStrCompatible;
+use crate::string::AsCStr;
 use crate::{
     architecture::CoreArchitecture,
     binary_view::BinaryView,
@@ -404,7 +404,7 @@ unsafe impl RefCountable for CoreRelocationHandler {
 
 pub(crate) fn register_relocation_handler<S, R, F>(arch: &CoreArchitecture, name: S, func: F)
 where
-    S: BnStrCompatible,
+    S: AsCStr,
     R: 'static + RelocationHandler<Handle = CustomRelocationHandlerHandle<R>> + Send + Sync + Sized,
     F: FnOnce(CustomRelocationHandlerHandle<R>, CoreRelocationHandler) -> R,
 {
@@ -503,8 +503,6 @@ where
             .into()
     }
 
-    let name = name.into_bytes_with_nul();
-
     let raw = Box::leak(Box::new(
         MaybeUninit::<RelocationHandlerBuilder<_>>::zeroed(),
     ));
@@ -529,7 +527,7 @@ where
 
         BNArchitectureRegisterRelocationHandler(
             arch.handle,
-            name.as_ref().as_ptr() as *const _,
+            name.as_cstr().as_ptr(),
             handle.handle().as_ref().0,
         );
     }
diff --git a/rust/src/render_layer.rs b/rust/src/render_layer.rs
index 80ecc6394..411c3eb26 100644
--- a/rust/src/render_layer.rs
+++ b/rust/src/render_layer.rs
@@ -6,9 +6,9 @@ use crate::flowgraph::FlowGraph;
 use crate::function::{Function, NativeBlock};
 use crate::linear_view::{LinearDisassemblyLine, LinearDisassemblyLineType, LinearViewObject};
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner};
-use crate::string::BnStrCompatible;
+use crate::string::AsCStr;
 use binaryninjacore_sys::*;
-use std::ffi::{c_char, c_void};
+use std::ffi::c_void;
 use std::ptr::NonNull;
 
 /// The state in which the [`RenderLayer`] will be registered with.
@@ -61,7 +61,7 @@ impl Default for RenderLayerDefaultState {
 }
 
 /// Register a [`RenderLayer`] with the API.
-pub fn register_render_layer<S: BnStrCompatible, T: RenderLayer>(
+pub fn register_render_layer<S: AsCStr, T: RenderLayer>(
     name: S,
     render_layer: T,
     default_state: RenderLayerDefaultState,
@@ -74,11 +74,7 @@ pub fn register_render_layer<S: BnStrCompatible, T: RenderLayer>(
         freeLines: Some(cb_free_lines),
     };
     let result = unsafe {
-        BNRegisterRenderLayer(
-            name.into_bytes_with_nul().as_ref().as_ptr() as *const _,
-            &mut callback,
-            default_state.into(),
-        )
+        BNRegisterRenderLayer(name.as_cstr().as_ptr(), &mut callback, default_state.into())
     };
     let core = CoreRenderLayer::from_raw(NonNull::new(result).unwrap());
     (render_layer, core)
@@ -303,9 +299,8 @@ impl CoreRenderLayer {
         unsafe { Array::new(result, count, ()) }
     }
 
-    pub fn render_layer_by_name<S: BnStrCompatible>(name: S) -> Option<CoreRenderLayer> {
-        let name_raw = name.into_bytes_with_nul();
-        let result = unsafe { BNGetRenderLayerByName(name_raw.as_ref().as_ptr() as *const c_char) };
+    pub fn render_layer_by_name<S: AsCStr>(name: S) -> Option<CoreRenderLayer> {
+        let result = unsafe { BNGetRenderLayerByName(name.as_cstr().as_ptr()) };
         NonNull::new(result).map(Self::from_raw)
     }
 
diff --git a/rust/src/repository.rs b/rust/src/repository.rs
index 135b23678..c2ea66800 100644
--- a/rust/src/repository.rs
+++ b/rust/src/repository.rs
@@ -9,7 +9,7 @@ use binaryninjacore_sys::*;
 
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
 use crate::repository::plugin::RepositoryPlugin;
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 pub use manager::RepositoryManager;
 
@@ -52,14 +52,9 @@ impl Repository {
         unsafe { Array::new(result, count, ()) }
     }
 
-    pub fn plugin_by_path<S: BnStrCompatible>(&self, path: S) -> Option<Ref<RepositoryPlugin>> {
-        let path = path.into_bytes_with_nul();
-        let result = unsafe {
-            BNRepositoryGetPluginByPath(
-                self.handle.as_ptr(),
-                path.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    pub fn plugin_by_path<S: AsCStr>(&self, path: S) -> Option<Ref<RepositoryPlugin>> {
+        let result =
+            unsafe { BNRepositoryGetPluginByPath(self.handle.as_ptr(), path.as_cstr().as_ptr()) };
         NonNull::new(result).map(|h| unsafe { RepositoryPlugin::ref_from_raw(h) })
     }
 
diff --git a/rust/src/repository/manager.rs b/rust/src/repository/manager.rs
index 598891625..aac7042e1 100644
--- a/rust/src/repository/manager.rs
+++ b/rust/src/repository/manager.rs
@@ -1,13 +1,12 @@
 use crate::rc::{Array, Ref, RefCountable};
 use crate::repository::Repository;
-use crate::string::BnStrCompatible;
+use crate::string::AsCStr;
 use binaryninjacore_sys::{
     BNCreateRepositoryManager, BNFreeRepositoryManager, BNGetRepositoryManager,
     BNNewRepositoryManagerReference, BNRepositoryGetRepositoryByPath, BNRepositoryManager,
     BNRepositoryManagerAddRepository, BNRepositoryManagerCheckForUpdates,
     BNRepositoryManagerGetDefaultRepository, BNRepositoryManagerGetRepositories,
 };
-use std::ffi::c_char;
 use std::fmt::Debug;
 use std::ptr::NonNull;
 
@@ -29,10 +28,8 @@ impl RepositoryManager {
         Ref::new(Self { handle })
     }
 
-    pub fn new<S: BnStrCompatible>(plugins_path: S) -> Ref<Self> {
-        let plugins_path = plugins_path.into_bytes_with_nul();
-        let result =
-            unsafe { BNCreateRepositoryManager(plugins_path.as_ref().as_ptr() as *const c_char) };
+    pub fn new<S: AsCStr>(plugins_path: S) -> Ref<Self> {
+        let result = unsafe { BNCreateRepositoryManager(plugins_path.as_cstr().as_ptr()) };
         unsafe { Self::ref_from_raw(NonNull::new(result).unwrap()) }
     }
 
@@ -61,29 +58,19 @@ impl RepositoryManager {
     /// * `repository_path` - path to where the repository will be stored on disk locally
     ///
     /// Returns true if the repository was successfully added, false otherwise.
-    pub fn add_repository<U: BnStrCompatible, P: BnStrCompatible>(
-        &self,
-        url: U,
-        repository_path: P,
-    ) -> bool {
-        let url = url.into_bytes_with_nul();
-        let repo_path = repository_path.into_bytes_with_nul();
+    pub fn add_repository<U: AsCStr, P: AsCStr>(&self, url: U, repository_path: P) -> bool {
         unsafe {
             BNRepositoryManagerAddRepository(
                 self.handle.as_ptr(),
-                url.as_ref().as_ptr() as *const c_char,
-                repo_path.as_ref().as_ptr() as *const c_char,
+                url.as_cstr().as_ptr(),
+                repository_path.as_cstr().as_ptr(),
             )
         }
     }
 
-    pub fn repository_by_path<P: BnStrCompatible>(&self, path: P) -> Option<Repository> {
-        let path = path.into_bytes_with_nul();
+    pub fn repository_by_path<P: AsCStr>(&self, path: P) -> Option<Repository> {
         let result = unsafe {
-            BNRepositoryGetRepositoryByPath(
-                self.handle.as_ptr(),
-                path.as_ref().as_ptr() as *const c_char,
-            )
+            BNRepositoryGetRepositoryByPath(self.handle.as_ptr(), path.as_cstr().as_ptr())
         };
         NonNull::new(result).map(|raw| unsafe { Repository::from_raw(raw) })
     }
diff --git a/rust/src/secrets_provider.rs b/rust/src/secrets_provider.rs
index e61cd4665..cd328517e 100644
--- a/rust/src/secrets_provider.rs
+++ b/rust/src/secrets_provider.rs
@@ -4,7 +4,7 @@ use std::fmt::Debug;
 use std::ptr::NonNull;
 
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 pub trait SecretsProvider {
     fn has_data(&mut self, key: &str) -> bool;
@@ -27,7 +27,6 @@ impl CoreSecretsProvider {
     /// Register a new provider
     pub fn new<C: SecretsProvider>(name: &str, callback: C) -> Self {
         // SAFETY: once create SecretsProvider is never dropped
-        let name = name.into_bytes_with_nul();
         let callback = Box::leak(Box::new(callback));
         let mut callbacks = BNSecretsProviderCallbacks {
             context: callback as *mut C as *mut c_void,
@@ -36,8 +35,7 @@ impl CoreSecretsProvider {
             storeData: Some(cb_store_data::<C>),
             deleteData: Some(cb_delete_data::<C>),
         };
-        let result =
-            unsafe { BNRegisterSecretsProvider(name.as_ptr() as *const c_char, &mut callbacks) };
+        let result = unsafe { BNRegisterSecretsProvider(name.as_cstr().as_ptr(), &mut callbacks) };
         unsafe { Self::from_raw(NonNull::new(result).unwrap()) }
     }
 
@@ -50,9 +48,8 @@ impl CoreSecretsProvider {
     }
 
     /// Retrieve a provider by name
-    pub fn by_name<S: BnStrCompatible>(name: S) -> Option<CoreSecretsProvider> {
-        let name = name.into_bytes_with_nul();
-        let result = unsafe { BNGetSecretsProviderByName(name.as_ref().as_ptr() as *const c_char) };
+    pub fn by_name<S: AsCStr>(name: S) -> Option<CoreSecretsProvider> {
+        let result = unsafe { BNGetSecretsProviderByName(name.as_cstr().as_ptr()) };
         NonNull::new(result).map(|h| unsafe { Self::from_raw(h) })
     }
 
@@ -63,44 +60,31 @@ impl CoreSecretsProvider {
     }
 
     /// Check if data for a specific key exists, but do not retrieve it
-    pub fn has_data<S: BnStrCompatible>(&self, key: S) -> bool {
-        let key = key.into_bytes_with_nul();
-        unsafe {
-            BNSecretsProviderHasData(self.handle.as_ptr(), key.as_ref().as_ptr() as *const c_char)
-        }
+    pub fn has_data<S: AsCStr>(&self, key: S) -> bool {
+        unsafe { BNSecretsProviderHasData(self.handle.as_ptr(), key.as_cstr().as_ptr()) }
     }
 
     /// Retrieve data for the given key, if it exists
-    pub fn get_data<S: BnStrCompatible>(&self, key: S) -> BnString {
-        let key = key.into_bytes_with_nul();
-        let result = unsafe {
-            BNGetSecretsProviderData(self.handle.as_ptr(), key.as_ref().as_ptr() as *const c_char)
-        };
+    pub fn get_data<S: AsCStr>(&self, key: S) -> BnString {
+        let result =
+            unsafe { BNGetSecretsProviderData(self.handle.as_ptr(), key.as_cstr().as_ptr()) };
         unsafe { BnString::from_raw(result) }
     }
 
     /// Store data with the given key
-    pub fn store_data<K: BnStrCompatible, V: BnStrCompatible>(&self, key: K, value: V) -> bool {
-        let key = key.into_bytes_with_nul();
-        let value = value.into_bytes_with_nul();
+    pub fn store_data<K: AsCStr, V: AsCStr>(&self, key: K, value: V) -> bool {
         unsafe {
             BNStoreSecretsProviderData(
                 self.handle.as_ptr(),
-                key.as_ref().as_ptr() as *const c_char,
-                value.as_ref().as_ptr() as *const c_char,
+                key.as_cstr().as_ptr(),
+                value.as_cstr().as_ptr(),
             )
         }
     }
 
     /// Delete stored data with the given key
-    pub fn delete_data<S: BnStrCompatible>(&self, key: S) -> bool {
-        let key = key.into_bytes_with_nul();
-        unsafe {
-            BNDeleteSecretsProviderData(
-                self.handle.as_ptr(),
-                key.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn delete_data<S: AsCStr>(&self, key: S) -> bool {
+        unsafe { BNDeleteSecretsProviderData(self.handle.as_ptr(), key.as_cstr().as_ptr()) }
     }
 }
 
diff --git a/rust/src/section.rs b/rust/src/section.rs
index c88ef9b0e..ef59f2fe9 100644
--- a/rust/src/section.rs
+++ b/rust/src/section.rs
@@ -14,7 +14,6 @@
 
 //! Sections are [crate::segment::Segment]s that are loaded into memory at run time
 
-use std::ffi::c_char;
 use std::fmt;
 use std::ops::Range;
 
@@ -270,44 +269,29 @@ impl SectionBuilder {
     }
 
     pub(crate) fn create(self, view: &BinaryView) {
-        let name = self.name.into_bytes_with_nul();
-        let ty = self.ty.into_bytes_with_nul();
-        let linked_section = self.linked_section.into_bytes_with_nul();
-        let info_section = self.info_section.into_bytes_with_nul();
-
         let start = self.range.start;
         let len = self.range.end.wrapping_sub(start);
 
+        let add_section = if self.is_auto {
+            BNAddAutoSection
+        } else {
+            BNAddUserSection
+        };
+
         unsafe {
-            if self.is_auto {
-                BNAddAutoSection(
-                    view.handle,
-                    name.as_ptr() as *const c_char,
-                    start,
-                    len,
-                    self.semantics.into(),
-                    ty.as_ptr() as *const c_char,
-                    self.align,
-                    self.entry_size,
-                    linked_section.as_ptr() as *const c_char,
-                    info_section.as_ptr() as *const c_char,
-                    self.info_data,
-                );
-            } else {
-                BNAddUserSection(
-                    view.handle,
-                    name.as_ptr() as *const c_char,
-                    start,
-                    len,
-                    self.semantics.into(),
-                    ty.as_ptr() as *const c_char,
-                    self.align,
-                    self.entry_size,
-                    linked_section.as_ptr() as *const c_char,
-                    info_section.as_ptr() as *const c_char,
-                    self.info_data,
-                );
-            }
+            add_section(
+                view.handle,
+                self.name.as_cstr().as_ptr(),
+                start,
+                len,
+                self.semantics.into(),
+                self.ty.as_cstr().as_ptr(),
+                self.align,
+                self.entry_size,
+                self.linked_section.as_cstr().as_ptr(),
+                self.info_section.as_cstr().as_ptr(),
+                self.info_data,
+            )
         }
     }
 }
diff --git a/rust/src/settings.rs b/rust/src/settings.rs
index 8c64f314e..e358ee3b0 100644
--- a/rust/src/settings.rs
+++ b/rust/src/settings.rs
@@ -20,7 +20,7 @@ use std::fmt::Debug;
 
 use crate::binary_view::BinaryView;
 use crate::rc::*;
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 use crate::function::Function;
 
@@ -44,48 +44,36 @@ impl Settings {
         Self::new_with_id(GLOBAL_INSTANCE_ID)
     }
 
-    pub fn new_with_id<S: BnStrCompatible>(instance_id: S) -> Ref<Self> {
-        let instance_id = instance_id.into_bytes_with_nul();
+    pub fn new_with_id<S: AsCStr>(instance_id: S) -> Ref<Self> {
         unsafe {
-            let handle = BNCreateSettings(instance_id.as_ref().as_ptr() as *mut _);
+            let handle = BNCreateSettings(instance_id.as_cstr().as_ptr());
             debug_assert!(!handle.is_null());
             Ref::new(Self { handle })
         }
     }
 
-    pub fn set_resource_id<S: BnStrCompatible>(&self, resource_id: S) {
-        let resource_id = resource_id.into_bytes_with_nul();
-        unsafe { BNSettingsSetResourceId(self.handle, resource_id.as_ref().as_ptr() as *mut _) };
+    pub fn set_resource_id<S: AsCStr>(&self, resource_id: S) {
+        unsafe { BNSettingsSetResourceId(self.handle, resource_id.as_cstr().as_ptr()) }
     }
 
     pub fn serialize_schema(&self) -> BnString {
         unsafe { BnString::from_raw(BNSettingsSerializeSchema(self.handle)) }
     }
 
-    pub fn deserialize_schema<S: BnStrCompatible>(&self, schema: S) -> bool {
+    pub fn deserialize_schema<S: AsCStr>(&self, schema: S) -> bool {
         self.deserialize_schema_with_scope(schema, SettingsScope::SettingsAutoScope)
     }
 
-    pub fn deserialize_schema_with_scope<S: BnStrCompatible>(
+    pub fn deserialize_schema_with_scope<S: AsCStr>(
         &self,
         schema: S,
         scope: SettingsScope,
     ) -> bool {
-        let schema = schema.into_bytes_with_nul();
-        unsafe {
-            BNSettingsDeserializeSchema(
-                self.handle,
-                schema.as_ref().as_ptr() as *mut _,
-                scope,
-                true,
-            )
-        }
+        unsafe { BNSettingsDeserializeSchema(self.handle, schema.as_cstr().as_ptr(), scope, true) }
     }
 
-    pub fn contains<S: BnStrCompatible>(&self, key: S) -> bool {
-        let key = key.into_bytes_with_nul();
-
-        unsafe { BNSettingsContains(self.handle, key.as_ref().as_ptr() as *mut _) }
+    pub fn contains<S: AsCStr>(&self, key: S) -> bool {
+        unsafe { BNSettingsContains(self.handle, key.as_cstr().as_ptr()) }
     }
 
     pub fn keys(&self) -> Array<BnString> {
@@ -97,16 +85,11 @@ impl Settings {
 
     // TODO Update the settings API to take an optional BinaryView or Function. Separate functions or...?
 
-    pub fn get_bool<S: BnStrCompatible>(&self, key: S) -> bool {
+    pub fn get_bool<S: AsCStr>(&self, key: S) -> bool {
         self.get_bool_with_opts(key, &mut QueryOptions::default())
     }
 
-    pub fn get_bool_with_opts<S: BnStrCompatible>(
-        &self,
-        key: S,
-        options: &mut QueryOptions,
-    ) -> bool {
-        let key = key.into_bytes_with_nul();
+    pub fn get_bool_with_opts<S: AsCStr>(&self, key: S, options: &mut QueryOptions) -> bool {
         let view_ptr = match options.view.as_ref() {
             Some(view) => view.handle,
             _ => std::ptr::null_mut(),
@@ -118,7 +101,7 @@ impl Settings {
         unsafe {
             BNSettingsGetBool(
                 self.handle,
-                key.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
                 view_ptr,
                 func_ptr,
                 &mut options.scope,
@@ -126,16 +109,11 @@ impl Settings {
         }
     }
 
-    pub fn get_double<S: BnStrCompatible>(&self, key: S) -> f64 {
+    pub fn get_double<S: AsCStr>(&self, key: S) -> f64 {
         self.get_double_with_opts(key, &mut QueryOptions::default())
     }
 
-    pub fn get_double_with_opts<S: BnStrCompatible>(
-        &self,
-        key: S,
-        options: &mut QueryOptions,
-    ) -> f64 {
-        let key = key.into_bytes_with_nul();
+    pub fn get_double_with_opts<S: AsCStr>(&self, key: S, options: &mut QueryOptions) -> f64 {
         let view_ptr = match options.view.as_ref() {
             Some(view) => view.handle,
             _ => std::ptr::null_mut(),
@@ -147,7 +125,7 @@ impl Settings {
         unsafe {
             BNSettingsGetDouble(
                 self.handle,
-                key.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
                 view_ptr,
                 func_ptr,
                 &mut options.scope,
@@ -155,16 +133,11 @@ impl Settings {
         }
     }
 
-    pub fn get_integer<S: BnStrCompatible>(&self, key: S) -> u64 {
+    pub fn get_integer<S: AsCStr>(&self, key: S) -> u64 {
         self.get_integer_with_opts(key, &mut QueryOptions::default())
     }
 
-    pub fn get_integer_with_opts<S: BnStrCompatible>(
-        &self,
-        key: S,
-        options: &mut QueryOptions,
-    ) -> u64 {
-        let key = key.into_bytes_with_nul();
+    pub fn get_integer_with_opts<S: AsCStr>(&self, key: S, options: &mut QueryOptions) -> u64 {
         let view_ptr = match options.view.as_ref() {
             Some(view) => view.handle,
             _ => std::ptr::null_mut(),
@@ -176,7 +149,7 @@ impl Settings {
         unsafe {
             BNSettingsGetUInt64(
                 self.handle,
-                key.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
                 view_ptr,
                 func_ptr,
                 &mut options.scope,
@@ -184,16 +157,11 @@ impl Settings {
         }
     }
 
-    pub fn get_string<S: BnStrCompatible>(&self, key: S) -> BnString {
+    pub fn get_string<S: AsCStr>(&self, key: S) -> BnString {
         self.get_string_with_opts(key, &mut QueryOptions::default())
     }
 
-    pub fn get_string_with_opts<S: BnStrCompatible>(
-        &self,
-        key: S,
-        options: &mut QueryOptions,
-    ) -> BnString {
-        let key = key.into_bytes_with_nul();
+    pub fn get_string_with_opts<S: AsCStr>(&self, key: S, options: &mut QueryOptions) -> BnString {
         let view_ptr = match options.view.as_ref() {
             Some(view) => view.handle,
             _ => std::ptr::null_mut(),
@@ -205,7 +173,7 @@ impl Settings {
         unsafe {
             BnString::from_raw(BNSettingsGetString(
                 self.handle,
-                key.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
                 view_ptr,
                 func_ptr,
                 &mut options.scope,
@@ -213,16 +181,15 @@ impl Settings {
         }
     }
 
-    pub fn get_string_list<S: BnStrCompatible>(&self, key: S) -> Array<BnString> {
+    pub fn get_string_list<S: AsCStr>(&self, key: S) -> Array<BnString> {
         self.get_string_list_with_opts(key, &mut QueryOptions::default())
     }
 
-    pub fn get_string_list_with_opts<S: BnStrCompatible>(
+    pub fn get_string_list_with_opts<S: AsCStr>(
         &self,
         key: S,
         options: &mut QueryOptions,
     ) -> Array<BnString> {
-        let key = key.into_bytes_with_nul();
         let view_ptr = match options.view.as_ref() {
             Some(view) => view.handle,
             _ => std::ptr::null_mut(),
@@ -236,7 +203,7 @@ impl Settings {
             Array::new(
                 BNSettingsGetStringList(
                     self.handle,
-                    key.as_ref().as_ptr() as *mut _,
+                    key.as_cstr().as_ptr(),
                     view_ptr,
                     func_ptr,
                     &mut options.scope,
@@ -248,16 +215,11 @@ impl Settings {
         }
     }
 
-    pub fn get_json<S: BnStrCompatible>(&self, key: S) -> BnString {
+    pub fn get_json<S: AsCStr>(&self, key: S) -> BnString {
         self.get_json_with_opts(key, &mut QueryOptions::default())
     }
 
-    pub fn get_json_with_opts<S: BnStrCompatible>(
-        &self,
-        key: S,
-        options: &mut QueryOptions,
-    ) -> BnString {
-        let key = key.into_bytes_with_nul();
+    pub fn get_json_with_opts<S: AsCStr>(&self, key: S, options: &mut QueryOptions) -> BnString {
         let view_ptr = match options.view.as_ref() {
             Some(view) => view.handle,
             _ => std::ptr::null_mut(),
@@ -269,7 +231,7 @@ impl Settings {
         unsafe {
             BnString::from_raw(BNSettingsGetJson(
                 self.handle,
-                key.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
                 view_ptr,
                 func_ptr,
                 &mut options.scope,
@@ -277,17 +239,11 @@ impl Settings {
         }
     }
 
-    pub fn set_bool<S: BnStrCompatible>(&self, key: S, value: bool) {
+    pub fn set_bool<S: AsCStr>(&self, key: S, value: bool) {
         self.set_bool_with_opts(key, value, &QueryOptions::default())
     }
 
-    pub fn set_bool_with_opts<S: BnStrCompatible>(
-        &self,
-        key: S,
-        value: bool,
-        options: &QueryOptions,
-    ) {
-        let key = key.into_bytes_with_nul();
+    pub fn set_bool_with_opts<S: AsCStr>(&self, key: S, value: bool, options: &QueryOptions) {
         let view_ptr = match options.view.as_ref() {
             Some(view) => view.handle,
             _ => std::ptr::null_mut(),
@@ -302,22 +258,16 @@ impl Settings {
                 view_ptr,
                 func_ptr,
                 options.scope,
-                key.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
                 value,
             );
         }
     }
 
-    pub fn set_double<S: BnStrCompatible>(&self, key: S, value: f64) {
+    pub fn set_double<S: AsCStr>(&self, key: S, value: f64) {
         self.set_double_with_opts(key, value, &QueryOptions::default())
     }
-    pub fn set_double_with_opts<S: BnStrCompatible>(
-        &self,
-        key: S,
-        value: f64,
-        options: &QueryOptions,
-    ) {
-        let key = key.into_bytes_with_nul();
+    pub fn set_double_with_opts<S: AsCStr>(&self, key: S, value: f64, options: &QueryOptions) {
         let view_ptr = match options.view.as_ref() {
             Some(view) => view.handle,
             _ => std::ptr::null_mut(),
@@ -332,23 +282,17 @@ impl Settings {
                 view_ptr,
                 func_ptr,
                 options.scope,
-                key.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
                 value,
             );
         }
     }
 
-    pub fn set_integer<S: BnStrCompatible>(&self, key: S, value: u64) {
+    pub fn set_integer<S: AsCStr>(&self, key: S, value: u64) {
         self.set_integer_with_opts(key, value, &QueryOptions::default())
     }
 
-    pub fn set_integer_with_opts<S: BnStrCompatible>(
-        &self,
-        key: S,
-        value: u64,
-        options: &QueryOptions,
-    ) {
-        let key = key.into_bytes_with_nul();
+    pub fn set_integer_with_opts<S: AsCStr>(&self, key: S, value: u64, options: &QueryOptions) {
         let view_ptr = match options.view.as_ref() {
             Some(view) => view.handle,
             _ => std::ptr::null_mut(),
@@ -363,24 +307,22 @@ impl Settings {
                 view_ptr,
                 func_ptr,
                 options.scope,
-                key.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
                 value,
             );
         }
     }
 
-    pub fn set_string<S1: BnStrCompatible, S2: BnStrCompatible>(&self, key: S1, value: S2) {
+    pub fn set_string<S1: AsCStr, S2: AsCStr>(&self, key: S1, value: S2) {
         self.set_string_with_opts(key, value, &QueryOptions::default())
     }
 
-    pub fn set_string_with_opts<S1: BnStrCompatible, S2: BnStrCompatible>(
+    pub fn set_string_with_opts<S1: AsCStr, S2: AsCStr>(
         &self,
         key: S1,
         value: S2,
         options: &QueryOptions,
     ) {
-        let key = key.into_bytes_with_nul();
-        let value = value.into_bytes_with_nul();
         let view_ptr = match options.view.as_ref() {
             Some(view) => view.handle,
             _ => std::ptr::null_mut(),
@@ -395,13 +337,13 @@ impl Settings {
                 view_ptr,
                 func_ptr,
                 options.scope,
-                key.as_ref().as_ptr() as *mut _,
-                value.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
+                value.as_cstr().as_ptr(),
             );
         }
     }
 
-    pub fn set_string_list<S1: BnStrCompatible, S2: BnStrCompatible, I: Iterator<Item = S2>>(
+    pub fn set_string_list<S1: AsCStr, S2: AsCStr, I: Iterator<Item = S2>>(
         &self,
         key: S1,
         value: I,
@@ -409,22 +351,15 @@ impl Settings {
         self.set_string_list_with_opts(key, value, &QueryOptions::default())
     }
 
-    pub fn set_string_list_with_opts<
-        S1: BnStrCompatible,
-        S2: BnStrCompatible,
-        I: Iterator<Item = S2>,
-    >(
+    pub fn set_string_list_with_opts<S1: AsCStr, S2: AsCStr, I: Iterator<Item = S2>>(
         &self,
         key: S1,
         value: I,
         options: &QueryOptions,
     ) -> bool {
-        let key = key.into_bytes_with_nul();
-        let raw_list: Vec<_> = value.map(|s| s.into_bytes_with_nul()).collect();
-        let mut raw_list_ptr: Vec<_> = raw_list
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect();
+        let value = value.collect::<Vec<_>>();
+        let raw_list: Vec<_> = value.iter().map(|s| s.as_cstr()).collect();
+        let mut raw_list_ptr: Vec<_> = raw_list.iter().map(|s| s.as_ptr()).collect();
 
         let view_ptr = match options.view.as_ref() {
             Some(view) => view.handle,
@@ -440,25 +375,23 @@ impl Settings {
                 view_ptr,
                 func_ptr,
                 options.scope,
-                key.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
                 raw_list_ptr.as_mut_ptr(),
                 raw_list_ptr.len(),
             )
         }
     }
 
-    pub fn set_json<S1: BnStrCompatible, S2: BnStrCompatible>(&self, key: S1, value: S2) -> bool {
+    pub fn set_json<S1: AsCStr, S2: AsCStr>(&self, key: S1, value: S2) -> bool {
         self.set_json_with_opts(key, value, &QueryOptions::default())
     }
 
-    pub fn set_json_with_opts<S1: BnStrCompatible, S2: BnStrCompatible>(
+    pub fn set_json_with_opts<S1: AsCStr, S2: AsCStr>(
         &self,
         key: S1,
         value: S2,
         options: &QueryOptions,
     ) -> bool {
-        let key = key.into_bytes_with_nul();
-        let value = value.into_bytes_with_nul();
         let view_ptr = match options.view.as_ref() {
             Some(view) => view.handle,
             _ => std::ptr::null_mut(),
@@ -473,38 +406,30 @@ impl Settings {
                 view_ptr,
                 func_ptr,
                 options.scope,
-                key.as_ref().as_ptr() as *mut _,
-                value.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
+                value.as_cstr().as_ptr(),
             )
         }
     }
 
-    pub fn get_property_string<S: BnStrCompatible>(&self, key: S, property: S) -> BnString {
-        let key = key.into_bytes_with_nul();
-        let property = property.into_bytes_with_nul();
+    pub fn get_property_string<S: AsCStr>(&self, key: S, property: S) -> BnString {
         unsafe {
             BnString::from_raw(BNSettingsQueryPropertyString(
                 self.handle,
-                key.as_ref().as_ptr() as *mut _,
-                property.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
+                property.as_cstr().as_ptr(),
             ))
         }
     }
 
-    pub fn get_property_string_list<S: BnStrCompatible>(
-        &self,
-        key: S,
-        property: S,
-    ) -> Array<BnString> {
-        let key = key.into_bytes_with_nul();
-        let property = property.into_bytes_with_nul();
+    pub fn get_property_string_list<S: AsCStr>(&self, key: S, property: S) -> Array<BnString> {
         let mut size: usize = 0;
         unsafe {
             Array::new(
                 BNSettingsQueryPropertyStringList(
                     self.handle,
-                    key.as_ref().as_ptr() as *mut _,
-                    property.as_ref().as_ptr() as *mut _,
+                    key.as_cstr().as_ptr(),
+                    property.as_cstr().as_ptr(),
                     &mut size,
                 ) as *mut *mut c_char,
                 size,
@@ -513,114 +438,87 @@ impl Settings {
         }
     }
 
-    pub fn update_bool_property<S: BnStrCompatible>(&self, key: S, property: S, value: bool) {
-        let key = key.into_bytes_with_nul();
-        let property = property.into_bytes_with_nul();
+    pub fn update_bool_property<S: AsCStr>(&self, key: S, property: S, value: bool) {
         unsafe {
             BNSettingsUpdateBoolProperty(
                 self.handle,
-                key.as_ref().as_ptr() as *mut _,
-                property.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
+                property.as_cstr().as_ptr(),
                 value,
             );
         }
     }
 
-    pub fn update_integer_property<S: BnStrCompatible>(&self, key: S, property: S, value: u64) {
-        let key = key.into_bytes_with_nul();
-        let property = property.into_bytes_with_nul();
+    pub fn update_integer_property<S: AsCStr>(&self, key: S, property: S, value: u64) {
         unsafe {
             BNSettingsUpdateUInt64Property(
                 self.handle,
-                key.as_ref().as_ptr() as *mut _,
-                property.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
+                property.as_cstr().as_ptr(),
                 value,
             );
         }
     }
 
-    pub fn update_double_property<S: BnStrCompatible>(&self, key: S, property: S, value: f64) {
-        let key = key.into_bytes_with_nul();
-        let property = property.into_bytes_with_nul();
+    pub fn update_double_property<S: AsCStr>(&self, key: S, property: S, value: f64) {
         unsafe {
             BNSettingsUpdateDoubleProperty(
                 self.handle,
-                key.as_ref().as_ptr() as *mut _,
-                property.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
+                property.as_cstr().as_ptr(),
                 value,
             );
         }
     }
 
-    pub fn update_string_property<S: BnStrCompatible>(&self, key: S, property: S, value: S) {
-        let key = key.into_bytes_with_nul();
-        let property = property.into_bytes_with_nul();
-        let value = value.into_bytes_with_nul();
+    pub fn update_string_property<S: AsCStr>(&self, key: S, property: S, value: S) {
         unsafe {
             BNSettingsUpdateStringProperty(
                 self.handle,
-                key.as_ref().as_ptr() as *mut _,
-                property.as_ref().as_ptr() as *mut _,
-                value.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
+                property.as_cstr().as_ptr(),
+                value.as_cstr().as_ptr(),
             );
         }
     }
 
-    pub fn update_string_list_property<S: BnStrCompatible, I: Iterator<Item = S>>(
+    pub fn update_string_list_property<S: AsCStr, I: Iterator<Item = S>>(
         &self,
         key: S,
         property: S,
         value: I,
     ) {
-        let key = key.into_bytes_with_nul();
-        let property = property.into_bytes_with_nul();
-        let raw_list: Vec<_> = value.map(|s| s.into_bytes_with_nul()).collect();
-        let mut raw_list_ptr: Vec<_> = raw_list
-            .iter()
-            .map(|s| s.as_ref().as_ptr() as *const c_char)
-            .collect();
+        let value = value.collect::<Vec<_>>();
+        let raw_list: Vec<_> = value.iter().map(|s| s.as_cstr()).collect();
+        let mut raw_list_ptr: Vec<_> = raw_list.iter().map(|s| s.as_ptr()).collect();
 
         unsafe {
             BNSettingsUpdateStringListProperty(
                 self.handle,
-                key.as_ref().as_ptr() as *mut _,
-                property.as_ref().as_ptr() as *mut _,
+                key.as_cstr().as_ptr(),
+                property.as_cstr().as_ptr(),
                 raw_list_ptr.as_mut_ptr(),
                 raw_list_ptr.len(),
             );
         }
     }
 
-    pub fn register_group<S1: BnStrCompatible, S2: BnStrCompatible>(
-        &self,
-        group: S1,
-        title: S2,
-    ) -> bool {
-        let group = group.into_bytes_with_nul();
-        let title = title.into_bytes_with_nul();
-
+    pub fn register_group<S1: AsCStr, S2: AsCStr>(&self, group: S1, title: S2) -> bool {
         unsafe {
             BNSettingsRegisterGroup(
                 self.handle,
-                group.as_ref().as_ptr() as *mut _,
-                title.as_ref().as_ptr() as *mut _,
+                group.as_cstr().as_ptr(),
+                title.as_cstr().as_ptr(),
             )
         }
     }
 
-    pub fn register_setting_json<S1: BnStrCompatible, S2: BnStrCompatible>(
-        &self,
-        group: S1,
-        properties: S2,
-    ) -> bool {
-        let group = group.into_bytes_with_nul();
-        let properties = properties.into_bytes_with_nul();
-
+    pub fn register_setting_json<S1: AsCStr, S2: AsCStr>(&self, group: S1, properties: S2) -> bool {
         unsafe {
             BNSettingsRegisterSetting(
                 self.handle,
-                group.as_ref().as_ptr() as *mut _,
-                properties.as_ref().as_ptr() as *mut _,
+                group.as_cstr().as_ptr(),
+                properties.as_cstr().as_ptr(),
             )
         }
     }
diff --git a/rust/src/string.rs b/rust/src/string.rs
index f92248516..f438d79e0 100644
--- a/rust/src/string.rs
+++ b/rust/src/string.rs
@@ -21,7 +21,6 @@ use std::ffi::{c_char, CStr, CString};
 use std::fmt;
 use std::hash::{Hash, Hasher};
 use std::mem;
-use std::ops::Deref;
 use std::path::{Path, PathBuf};
 
 // TODO: Remove or refactor this.
@@ -44,29 +43,23 @@ pub(crate) fn strings_to_string_list(strings: &[String]) -> *mut *mut c_char {
     unsafe { BNAllocStringList(raw_str_list.as_mut_ptr(), raw_str_list.len()) }
 }
 
-/// Is the equivalent of `core::ffi::CString` but using the alloc and free from `binaryninjacore-sys`.
+/// A nul-terminated C string allocated by the core.
+///
+/// Received from a variety of core function calls, and must be used when giving strings to the
+/// core from many core-invoked callbacks.
+///
+/// These are strings we're responsible for freeing, such as strings allocated by the core and
+/// given to us through the API and then forgotten about by the core.
 #[repr(transparent)]
 pub struct BnString {
     raw: *mut c_char,
 }
 
-/// A nul-terminated C string allocated by the core.
-///
-/// Received from a variety of core function calls, and
-/// must be used when giving strings to the core from many
-/// core-invoked callbacks.
-///
-/// These are strings we're responsible for freeing, such as
-/// strings allocated by the core and given to us through the API
-/// and then forgotten about by the core.
 impl BnString {
-    pub fn new<S: BnStrCompatible>(s: S) -> Self {
+    pub fn new<S: AsCStr>(s: S) -> Self {
         use binaryninjacore_sys::BNAllocString;
-        let raw = s.into_bytes_with_nul();
-        unsafe {
-            let ptr = raw.as_ref().as_ptr() as *mut _;
-            Self::from_raw(BNAllocString(ptr))
-        }
+        let raw = s.as_cstr();
+        unsafe { Self::from_raw(BNAllocString(raw.as_ptr())) }
     }
 
     /// Construct a BnString from an owned const char* allocated by BNAllocString
@@ -76,28 +69,32 @@ impl BnString {
 
     /// Consumes the `BnString`, returning a raw pointer to the string.
     ///
-    /// After calling this function, the caller is responsible for the
-    /// memory previously managed by the `BnString`.
+    /// After calling this function, the caller is responsible for freeing the memory previously
+    /// managed by the `BnString`.
     ///
-    /// This is typically used to pass a string back through the core where the core is expected to free.
+    /// This is typically used to pass a string back through the core where the core is expected to
+    /// free.
     pub fn into_raw(value: Self) -> *mut c_char {
         let res = value.raw;
-        // we're surrendering ownership over the *mut c_char to
-        // the core, so ensure we don't free it
+        // surrendering ownership of the pointer to the core, so ensure we don't free it
         mem::forget(value);
         res
     }
 
+    pub fn as_c_str(&self) -> &CStr {
+        unsafe { CStr::from_ptr(self.raw) }
+    }
+
     pub fn as_str(&self) -> &str {
-        unsafe { CStr::from_ptr(self.raw).to_str().unwrap() }
+        self.as_c_str().to_str().unwrap()
     }
 
     pub fn as_bytes(&self) -> &[u8] {
-        self.as_str().as_bytes()
+        self.as_c_str().to_bytes()
     }
 
-    pub fn as_bytes_with_null(&self) -> &[u8] {
-        self.deref().to_bytes()
+    pub fn as_ptr(&self) -> *const c_char {
+        self.raw
     }
 
     pub fn len(&self) -> usize {
@@ -129,29 +126,27 @@ impl Clone for BnString {
     }
 }
 
-impl Deref for BnString {
-    type Target = CStr;
-
-    fn deref(&self) -> &CStr {
-        unsafe { CStr::from_ptr(self.raw) }
+impl Hash for BnString {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.raw.hash(state)
     }
 }
 
-impl AsRef<[u8]> for BnString {
-    fn as_ref(&self) -> &[u8] {
-        self.to_bytes_with_nul()
+impl PartialEq for BnString {
+    fn eq(&self, other: &Self) -> bool {
+        self.as_c_str() == other.as_c_str()
     }
 }
 
-impl Hash for BnString {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.raw.hash(state)
+impl PartialEq<CStr> for BnString {
+    fn eq(&self, other: &CStr) -> bool {
+        self.as_c_str() == other
     }
 }
 
-impl PartialEq for BnString {
-    fn eq(&self, other: &Self) -> bool {
-        self.deref() == other.deref()
+impl PartialEq<str> for BnString {
+    fn eq(&self, other: &str) -> bool {
+        self.as_str() == other
     }
 }
 
@@ -159,13 +154,13 @@ impl Eq for BnString {}
 
 impl fmt::Display for BnString {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}", self.to_string_lossy())
+        self.as_c_str().to_string_lossy().fmt(f)
     }
 }
 
 impl fmt::Debug for BnString {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        self.to_string_lossy().fmt(f)
+        self.as_c_str().to_string_lossy().fmt(f)
     }
 }
 
@@ -186,111 +181,83 @@ unsafe impl CoreArrayProviderInner for BnString {
     }
 }
 
-pub unsafe trait BnStrCompatible {
-    type Result: AsRef<[u8]>;
-
-    fn into_bytes_with_nul(self) -> Self::Result;
+pub trait AsCStr {
+    fn as_cstr(&self) -> Cow<'_, CStr>;
 }
 
-unsafe impl<'a> BnStrCompatible for &'a CStr {
-    type Result = &'a [u8];
-
-    fn into_bytes_with_nul(self) -> Self::Result {
-        self.to_bytes_with_nul()
+impl AsCStr for CStr {
+    fn as_cstr(&self) -> Cow<'_, CStr> {
+        self.into()
     }
 }
 
-unsafe impl BnStrCompatible for BnString {
-    type Result = Self;
-
-    fn into_bytes_with_nul(self) -> Self::Result {
-        self
+impl AsCStr for CString {
+    fn as_cstr(&self) -> Cow<'_, CStr> {
+        self.into()
     }
 }
 
-unsafe impl BnStrCompatible for CString {
-    type Result = Vec<u8>;
-
-    fn into_bytes_with_nul(self) -> Self::Result {
-        self.into_bytes_with_nul()
+impl AsCStr for BnString {
+    fn as_cstr(&self) -> Cow<'_, CStr> {
+        self.as_c_str().into()
     }
 }
 
-unsafe impl BnStrCompatible for &str {
-    type Result = Vec<u8>;
-
-    fn into_bytes_with_nul(self) -> Self::Result {
-        let ret = CString::new(self).expect("can't pass strings with internal nul bytes to core!");
-        ret.into_bytes_with_nul()
+impl AsCStr for str {
+    fn as_cstr(&self) -> Cow<'_, CStr> {
+        CString::new(self)
+            .expect("can't pass strings with internal null bytes to the core!")
+            .into()
     }
 }
 
-unsafe impl BnStrCompatible for String {
-    type Result = Vec<u8>;
-
-    fn into_bytes_with_nul(self) -> Self::Result {
-        self.as_str().into_bytes_with_nul()
+impl AsCStr for String {
+    fn as_cstr(&self) -> Cow<'_, CStr> {
+        self.as_str().as_cstr()
     }
 }
 
-unsafe impl BnStrCompatible for &String {
-    type Result = Vec<u8>;
-
-    fn into_bytes_with_nul(self) -> Self::Result {
-        self.as_str().into_bytes_with_nul()
+impl AsCStr for Cow<'_, str> {
+    fn as_cstr(&self) -> Cow<'_, CStr> {
+        self.as_ref().as_cstr()
     }
 }
 
-unsafe impl<'a> BnStrCompatible for &'a Cow<'a, str> {
-    type Result = Vec<u8>;
-
-    fn into_bytes_with_nul(self) -> Self::Result {
-        self.to_string().into_bytes_with_nul()
+impl AsCStr for Path {
+    fn as_cstr(&self) -> Cow<'_, CStr> {
+        CString::new(self.as_os_str().as_encoded_bytes())
+            .expect("can't pass paths with internal null bytes to the core!")
+            .into()
     }
 }
 
-unsafe impl BnStrCompatible for Cow<'_, str> {
-    type Result = Vec<u8>;
-
-    fn into_bytes_with_nul(self) -> Self::Result {
-        self.to_string().into_bytes_with_nul()
+impl AsCStr for PathBuf {
+    fn as_cstr(&self) -> Cow<'_, CStr> {
+        self.as_path().as_cstr()
     }
 }
 
-unsafe impl BnStrCompatible for &QualifiedName {
-    type Result = Vec<u8>;
-
-    fn into_bytes_with_nul(self) -> Self::Result {
-        self.to_string().into_bytes_with_nul()
+impl AsCStr for QualifiedName {
+    fn as_cstr(&self) -> Cow<'_, CStr> {
+        // Simply deferring to `self.to_string().as_cstr()` makes the borrow checker angry
+        CString::new(self.to_string()).unwrap().into()
     }
 }
 
-unsafe impl BnStrCompatible for PathBuf {
-    type Result = Vec<u8>;
-
-    fn into_bytes_with_nul(self) -> Self::Result {
-        self.as_path().into_bytes_with_nul()
-    }
-}
-
-unsafe impl BnStrCompatible for &Path {
-    type Result = Vec<u8>;
-
-    fn into_bytes_with_nul(self) -> Self::Result {
-        let ret = CString::new(self.as_os_str().as_encoded_bytes())
-            .expect("can't pass paths with internal nul bytes to core!");
-        ret.into_bytes_with_nul()
+impl<T: AsCStr + ?Sized> AsCStr for &T {
+    fn as_cstr(&self) -> Cow<'_, CStr> {
+        (*self).as_cstr()
     }
 }
 
 pub trait IntoJson {
-    type Output: BnStrCompatible;
+    type Output: AsCStr;
 
     fn get_json_string(self) -> Result<Self::Output, ()>;
 }
 
-impl<S: BnStrCompatible> IntoJson for S {
-    type Output = S;
+impl<S: AsCStr> IntoJson for S {
+    type Output = Self;
 
     fn get_json_string(self) -> Result<Self::Output, ()> {
         Ok(self)
diff --git a/rust/src/symbol.rs b/rust/src/symbol.rs
index 2ff92db53..1c1a49986 100644
--- a/rust/src/symbol.rs
+++ b/rust/src/symbol.rs
@@ -153,64 +153,27 @@ impl SymbolBuilder {
     }
 
     pub fn create(self) -> Ref<Symbol> {
-        let raw_name = self.raw_name.into_bytes_with_nul();
-        let short_name = self.short_name.map(|s| s.into_bytes_with_nul());
-        let full_name = self.full_name.map(|s| s.into_bytes_with_nul());
+        let raw_name = self.raw_name.as_cstr();
+        let short_name = self
+            .short_name
+            .as_ref()
+            .map_or_else(|| raw_name.clone(), |s| s.as_cstr());
+        let full_name = self
+            .full_name
+            .as_ref()
+            .map_or_else(|| raw_name.clone(), |s| s.as_cstr());
 
-        // Lifetimes, man
-        let raw_name = raw_name.as_ptr() as _;
         unsafe {
-            if let Some(short_name) = short_name {
-                if let Some(full_name) = full_name {
-                    let res = BNCreateSymbol(
-                        self.ty.into(),
-                        short_name.as_ptr() as _,
-                        full_name.as_ptr() as _,
-                        raw_name,
-                        self.addr,
-                        self.binding.into(),
-                        ptr::null_mut(),
-                        self.ordinal,
-                    );
-                    Symbol::ref_from_raw(res)
-                } else {
-                    let res = BNCreateSymbol(
-                        self.ty.into(),
-                        short_name.as_ptr() as _,
-                        raw_name,
-                        raw_name,
-                        self.addr,
-                        self.binding.into(),
-                        ptr::null_mut(),
-                        self.ordinal,
-                    );
-                    Symbol::ref_from_raw(res)
-                }
-            } else if let Some(full_name) = full_name {
-                let res = BNCreateSymbol(
-                    self.ty.into(),
-                    raw_name,
-                    full_name.as_ptr() as _,
-                    raw_name,
-                    self.addr,
-                    self.binding.into(),
-                    ptr::null_mut(),
-                    self.ordinal,
-                );
-                Symbol::ref_from_raw(res)
-            } else {
-                let res = BNCreateSymbol(
-                    self.ty.into(),
-                    raw_name,
-                    raw_name,
-                    raw_name,
-                    self.addr,
-                    self.binding.into(),
-                    ptr::null_mut(),
-                    self.ordinal,
-                );
-                Symbol::ref_from_raw(res)
-            }
+            Symbol::ref_from_raw(BNCreateSymbol(
+                self.ty.into(),
+                short_name.as_ptr(),
+                full_name.as_ptr(),
+                raw_name.as_ptr(),
+                self.addr,
+                self.binding.into(),
+                ptr::null_mut(),
+                self.ordinal,
+            ))
         }
     }
 }
diff --git a/rust/src/tags.rs b/rust/src/tags.rs
index 2db3a58cb..cddf62d62 100644
--- a/rust/src/tags.rs
+++ b/rust/src/tags.rs
@@ -42,9 +42,8 @@ impl Tag {
         Ref::new(Self { handle })
     }
 
-    pub fn new<S: BnStrCompatible>(t: &TagType, data: S) -> Ref<Self> {
-        let data = data.into_bytes_with_nul();
-        unsafe { Self::ref_from_raw(BNCreateTag(t.handle, data.as_ref().as_ptr() as *mut _)) }
+    pub fn new<S: AsCStr>(t: &TagType, data: S) -> Ref<Self> {
+        unsafe { Self::ref_from_raw(BNCreateTag(t.handle, data.as_cstr().as_ptr())) }
     }
 
     pub fn id(&self) -> BnString {
@@ -59,11 +58,8 @@ impl Tag {
         unsafe { TagType::ref_from_raw(BNTagGetType(self.handle)) }
     }
 
-    pub fn set_data<S: BnStrCompatible>(&self, data: S) {
-        let data = data.into_bytes_with_nul();
-        unsafe {
-            BNTagSetData(self.handle, data.as_ref().as_ptr() as *mut _);
-        }
+    pub fn set_data<S: AsCStr>(&self, data: S) {
+        unsafe { BNTagSetData(self.handle, data.as_cstr().as_ptr()) }
     }
 }
 
@@ -134,11 +130,7 @@ impl TagType {
         Ref::new(Self { handle })
     }
 
-    pub fn create<N: BnStrCompatible, I: BnStrCompatible>(
-        view: &BinaryView,
-        name: N,
-        icon: I,
-    ) -> Ref<Self> {
+    pub fn create<N: AsCStr, I: AsCStr>(view: &BinaryView, name: N, icon: I) -> Ref<Self> {
         let tag_type = unsafe { Self::ref_from_raw(BNCreateTagType(view.handle)) };
         tag_type.set_name(name);
         tag_type.set_icon(icon);
@@ -153,22 +145,16 @@ impl TagType {
         unsafe { BnString::from_raw(BNTagTypeGetIcon(self.handle)) }
     }
 
-    pub fn set_icon<S: BnStrCompatible>(&self, icon: S) {
-        let icon = icon.into_bytes_with_nul();
-        unsafe {
-            BNTagTypeSetIcon(self.handle, icon.as_ref().as_ptr() as *mut _);
-        }
+    pub fn set_icon<S: AsCStr>(&self, icon: S) {
+        unsafe { BNTagTypeSetIcon(self.handle, icon.as_cstr().as_ptr()) }
     }
 
     pub fn name(&self) -> BnString {
         unsafe { BnString::from_raw(BNTagTypeGetName(self.handle)) }
     }
 
-    pub fn set_name<S: BnStrCompatible>(&self, name: S) {
-        let name = name.into_bytes_with_nul();
-        unsafe {
-            BNTagTypeSetName(self.handle, name.as_ref().as_ptr() as *mut _);
-        }
+    pub fn set_name<S: AsCStr>(&self, name: S) {
+        unsafe { BNTagTypeSetName(self.handle, name.as_cstr().as_ptr()) }
     }
 
     pub fn visible(&self) -> bool {
@@ -183,11 +169,8 @@ impl TagType {
         unsafe { BNTagTypeGetType(self.handle) }
     }
 
-    pub fn set_type<S: BnStrCompatible>(&self, t: S) {
-        let t = t.into_bytes_with_nul();
-        unsafe {
-            BNTagTypeSetName(self.handle, t.as_ref().as_ptr() as *mut _);
-        }
+    pub fn set_type<S: AsCStr>(&self, t: S) {
+        unsafe { BNTagTypeSetName(self.handle, t.as_cstr().as_ptr()) }
     }
 
     pub fn view(&self) -> Ref<BinaryView> {
diff --git a/rust/src/template_simplifier.rs b/rust/src/template_simplifier.rs
index dd815f69a..615ffd327 100644
--- a/rust/src/template_simplifier.rs
+++ b/rust/src/template_simplifier.rs
@@ -1,20 +1,15 @@
 use crate::{
-    string::{BnStrCompatible, BnString},
+    string::{AsCStr, BnString},
     types::QualifiedName,
 };
 use binaryninjacore_sys::{BNRustSimplifyStrToFQN, BNRustSimplifyStrToStr};
 
-pub fn simplify_str_to_str<S: BnStrCompatible>(input: S) -> BnString {
-    let name = input.into_bytes_with_nul();
-    unsafe { BnString::from_raw(BNRustSimplifyStrToStr(name.as_ref().as_ptr() as *mut _)) }
+pub fn simplify_str_to_str<S: AsCStr>(input: S) -> BnString {
+    unsafe { BnString::from_raw(BNRustSimplifyStrToStr(input.as_cstr().as_ptr())) }
 }
 
-pub fn simplify_str_to_fqn<S: BnStrCompatible>(input: S, simplify: bool) -> QualifiedName {
-    let name = input.into_bytes_with_nul();
+pub fn simplify_str_to_fqn<S: AsCStr>(input: S, simplify: bool) -> QualifiedName {
     unsafe {
-        QualifiedName::from_owned_raw(BNRustSimplifyStrToFQN(
-            name.as_ref().as_ptr() as *mut _,
-            simplify,
-        ))
+        QualifiedName::from_owned_raw(BNRustSimplifyStrToFQN(input.as_cstr().as_ptr(), simplify))
     }
 }
diff --git a/rust/src/type_archive.rs b/rust/src/type_archive.rs
index f10cda504..c37b10f85 100644
--- a/rust/src/type_archive.rs
+++ b/rust/src/type_archive.rs
@@ -10,7 +10,7 @@ use crate::data_buffer::DataBuffer;
 use crate::metadata::Metadata;
 use crate::platform::Platform;
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{raw_to_string, BnStrCompatible, BnString};
+use crate::string::{raw_to_string, AsCStr, BnString};
 use crate::type_container::TypeContainer;
 use crate::types::{QualifiedName, QualifiedNameAndType, QualifiedNameTypeAndId, Type};
 
@@ -65,8 +65,7 @@ impl TypeArchive {
 
     /// Open the Type Archive at the given path, if it exists.
     pub fn open(path: impl AsRef<Path>) -> Option<Ref<TypeArchive>> {
-        let raw_path = path.as_ref().into_bytes_with_nul();
-        let handle = unsafe { BNOpenTypeArchive(raw_path.as_ptr() as *const c_char) };
+        let handle = unsafe { BNOpenTypeArchive(path.as_ref().as_cstr().as_ptr()) };
         NonNull::new(handle).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) })
     }
 
@@ -74,36 +73,32 @@ impl TypeArchive {
     ///
     /// If the file has already been created and is not a valid type archive this will return `None`.
     pub fn create(path: impl AsRef<Path>, platform: &Platform) -> Option<Ref<TypeArchive>> {
-        let raw_path = path.as_ref().into_bytes_with_nul();
         let handle =
-            unsafe { BNCreateTypeArchive(raw_path.as_ptr() as *const c_char, platform.handle) };
+            unsafe { BNCreateTypeArchive(path.as_ref().as_cstr().as_ptr(), platform.handle) };
         NonNull::new(handle).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) })
     }
 
     /// Create a Type Archive at the given path and id, returning None if it could not be created.
     ///
     /// If the file has already been created and is not a valid type archive this will return `None`.
-    pub fn create_with_id<I: BnStrCompatible>(
+    pub fn create_with_id<I: AsCStr>(
         path: impl AsRef<Path>,
         id: I,
         platform: &Platform,
     ) -> Option<Ref<TypeArchive>> {
-        let raw_path = path.as_ref().into_bytes_with_nul();
-        let id = id.into_bytes_with_nul();
         let handle = unsafe {
             BNCreateTypeArchiveWithId(
-                raw_path.as_ptr() as *const c_char,
+                path.as_ref().as_cstr().as_ptr(),
                 platform.handle,
-                id.as_ref().as_ptr() as *const c_char,
+                id.as_cstr().as_ptr(),
             )
         };
         NonNull::new(handle).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) })
     }
 
     /// Get a reference to the Type Archive with the known id, if one exists.
-    pub fn lookup_by_id<S: BnStrCompatible>(id: S) -> Option<Ref<TypeArchive>> {
-        let id = id.into_bytes_with_nul();
-        let handle = unsafe { BNLookupTypeArchiveById(id.as_ref().as_ptr() as *const c_char) };
+    pub fn lookup_by_id<S: AsCStr>(id: S) -> Option<Ref<TypeArchive>> {
+        let handle = unsafe { BNLookupTypeArchiveById(id.as_cstr().as_ptr()) };
         NonNull::new(handle).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) })
     }
 
@@ -155,7 +150,7 @@ impl TypeArchive {
     }
 
     /// Get the ids of the parents to the given snapshot
-    pub fn get_snapshot_parent_ids<S: BnStrCompatible>(
+    pub fn get_snapshot_parent_ids(
         &self,
         snapshot: &TypeArchiveSnapshotId,
     ) -> Option<Array<BnString>> {
@@ -171,7 +166,7 @@ impl TypeArchive {
     }
 
     /// Get the ids of the children to the given snapshot
-    pub fn get_snapshot_child_ids<S: BnStrCompatible>(
+    pub fn get_snapshot_child_ids(
         &self,
         snapshot: &TypeArchiveSnapshotId,
     ) -> Option<Array<BnString>> {
@@ -223,26 +218,18 @@ impl TypeArchive {
     /// * `old_name` - Old type name in archive
     /// * `new_name` - New type name
     pub fn rename_type(&self, old_name: QualifiedName, new_name: QualifiedName) -> bool {
-        if let Some(id) = self.get_type_id(old_name) {
-            self.rename_type_by_id(id, new_name)
-        } else {
-            false
-        }
+        self.get_type_id(old_name)
+            .is_some_and(|id| self.rename_type_by_id(id, new_name))
     }
 
     /// Change the name of an existing type in the type archive. Returns false if failed.
     ///
     /// * `id` - Old id of type in archive
     /// * `new_name` - New type name
-    pub fn rename_type_by_id<S: BnStrCompatible>(&self, id: S, new_name: QualifiedName) -> bool {
-        let id = id.into_bytes_with_nul();
+    pub fn rename_type_by_id<S: AsCStr>(&self, id: S, new_name: QualifiedName) -> bool {
         let raw_name = QualifiedName::into_raw(new_name);
         let result = unsafe {
-            BNRenameTypeArchiveType(
-                self.handle.as_ptr(),
-                id.as_ref().as_ptr() as *const c_char,
-                &raw_name,
-            )
+            BNRenameTypeArchiveType(self.handle.as_ptr(), id.as_cstr().as_ptr(), &raw_name)
         };
         QualifiedName::free_raw(raw_name);
         result
@@ -250,26 +237,21 @@ impl TypeArchive {
 
     /// Delete an existing type in the type archive.
     pub fn delete_type(&self, name: QualifiedName) -> bool {
-        if let Some(type_id) = self.get_type_id(name) {
-            self.delete_type_by_id(type_id)
-        } else {
-            false
-        }
+        self.get_type_id(name)
+            .is_some_and(|id| self.delete_type_by_id(id))
     }
 
     /// Delete an existing type in the type archive.
-    pub fn delete_type_by_id<S: BnStrCompatible>(&self, id: S) -> bool {
-        let id = id.into_bytes_with_nul();
-        let result = unsafe {
-            BNDeleteTypeArchiveType(self.handle.as_ptr(), id.as_ref().as_ptr() as *const c_char)
-        };
+    pub fn delete_type_by_id<S: AsCStr>(&self, id: S) -> bool {
+        let result =
+            unsafe { BNDeleteTypeArchiveType(self.handle.as_ptr(), id.as_cstr().as_ptr()) };
         result
     }
 
     /// Retrieve a stored type in the archive
     ///
     /// * `name` - Type name
-    pub fn get_type_by_name<S: BnStrCompatible>(&self, name: QualifiedName) -> Option<Ref<Type>> {
+    pub fn get_type_by_name<S: AsCStr>(&self, name: QualifiedName) -> Option<Ref<Type>> {
         self.get_type_by_name_from_snapshot(name, &TypeArchiveSnapshotId::unset())
     }
 
@@ -297,7 +279,7 @@ impl TypeArchive {
     /// Retrieve a stored type in the archive by id
     ///
     /// * `id` - Type id
-    pub fn get_type_by_id<I: BnStrCompatible>(&self, id: I) -> Option<Ref<Type>> {
+    pub fn get_type_by_id<I: AsCStr>(&self, id: I) -> Option<Ref<Type>> {
         self.get_type_by_id_from_snapshot(id, &TypeArchiveSnapshotId::unset())
     }
 
@@ -305,16 +287,15 @@ impl TypeArchive {
     ///
     /// * `id` - Type id
     /// * `snapshot` - Snapshot id to search for types
-    pub fn get_type_by_id_from_snapshot<I: BnStrCompatible>(
+    pub fn get_type_by_id_from_snapshot<I: AsCStr>(
         &self,
         id: I,
         snapshot: &TypeArchiveSnapshotId,
     ) -> Option<Ref<Type>> {
-        let id = id.into_bytes_with_nul();
         let result = unsafe {
             BNGetTypeArchiveTypeById(
                 self.handle.as_ptr(),
-                id.as_ref().as_ptr() as *const c_char,
+                id.as_cstr().as_ptr(),
                 snapshot.0.as_ptr() as *const c_char,
             )
         };
@@ -324,7 +305,7 @@ impl TypeArchive {
     /// Retrieve a type's name by its id
     ///
     /// * `id` - Type id
-    pub fn get_type_name_by_id<I: BnStrCompatible>(&self, id: I) -> QualifiedName {
+    pub fn get_type_name_by_id<I: AsCStr>(&self, id: I) -> QualifiedName {
         self.get_type_name_by_id_from_snapshot(id, &TypeArchiveSnapshotId::unset())
     }
 
@@ -332,16 +313,15 @@ impl TypeArchive {
     ///
     /// * `id` - Type id
     /// * `snapshot` - Snapshot id to search for types
-    pub fn get_type_name_by_id_from_snapshot<I: BnStrCompatible>(
+    pub fn get_type_name_by_id_from_snapshot<I: AsCStr>(
         &self,
         id: I,
         snapshot: &TypeArchiveSnapshotId,
     ) -> QualifiedName {
-        let id = id.into_bytes_with_nul();
         let result = unsafe {
             BNGetTypeArchiveTypeName(
                 self.handle.as_ptr(),
-                id.as_ref().as_ptr() as *const c_char,
+                id.as_cstr().as_ptr(),
                 snapshot.0.as_ptr() as *const c_char,
             )
         };
@@ -478,7 +458,7 @@ impl TypeArchive {
     /// Get all types a given type references directly
     ///
     /// * `id` - Source type id
-    pub fn get_outgoing_direct_references<I: BnStrCompatible>(&self, id: I) -> Array<BnString> {
+    pub fn get_outgoing_direct_references<I: AsCStr>(&self, id: I) -> Array<BnString> {
         self.get_outgoing_direct_references_from_snapshot(id, &TypeArchiveSnapshotId::unset())
     }
 
@@ -486,17 +466,16 @@ impl TypeArchive {
     ///
     /// * `id` - Source type id
     /// * `snapshot` - Snapshot id to search for types
-    pub fn get_outgoing_direct_references_from_snapshot<I: BnStrCompatible>(
+    pub fn get_outgoing_direct_references_from_snapshot<I: AsCStr>(
         &self,
         id: I,
         snapshot: &TypeArchiveSnapshotId,
     ) -> Array<BnString> {
-        let id = id.into_bytes_with_nul();
         let mut count = 0;
         let result = unsafe {
             BNGetTypeArchiveOutgoingDirectTypeReferences(
                 self.handle.as_ptr(),
-                id.as_ref().as_ptr() as *const c_char,
+                id.as_cstr().as_ptr(),
                 snapshot.0.as_ptr() as *const c_char,
                 &mut count,
             )
@@ -508,7 +487,7 @@ impl TypeArchive {
     /// Get all types a given type references, and any types that the referenced types reference
     ///
     /// * `id` - Source type id
-    pub fn get_outgoing_recursive_references<I: BnStrCompatible>(&self, id: I) -> Array<BnString> {
+    pub fn get_outgoing_recursive_references<I: AsCStr>(&self, id: I) -> Array<BnString> {
         self.get_outgoing_recursive_references_from_snapshot(id, &TypeArchiveSnapshotId::unset())
     }
 
@@ -516,17 +495,16 @@ impl TypeArchive {
     ///
     /// * `id` - Source type id
     /// * `snapshot` - Snapshot id to search for types
-    pub fn get_outgoing_recursive_references_from_snapshot<I: BnStrCompatible>(
+    pub fn get_outgoing_recursive_references_from_snapshot<I: AsCStr>(
         &self,
         id: I,
         snapshot: &TypeArchiveSnapshotId,
     ) -> Array<BnString> {
-        let id = id.into_bytes_with_nul();
         let mut count = 0;
         let result = unsafe {
             BNGetTypeArchiveOutgoingRecursiveTypeReferences(
                 self.handle.as_ptr(),
-                id.as_ref().as_ptr() as *const c_char,
+                id.as_cstr().as_ptr(),
                 snapshot.0.as_ptr() as *const c_char,
                 &mut count,
             )
@@ -538,7 +516,7 @@ impl TypeArchive {
     /// Get all types that reference a given type
     ///
     /// * `id` - Target type id
-    pub fn get_incoming_direct_references<I: BnStrCompatible>(&self, id: I) -> Array<BnString> {
+    pub fn get_incoming_direct_references<I: AsCStr>(&self, id: I) -> Array<BnString> {
         self.get_incoming_direct_references_with_snapshot(id, &TypeArchiveSnapshotId::unset())
     }
 
@@ -546,17 +524,16 @@ impl TypeArchive {
     ///
     /// * `id` - Target type id
     /// * `snapshot` - Snapshot id to search for types
-    pub fn get_incoming_direct_references_with_snapshot<I: BnStrCompatible>(
+    pub fn get_incoming_direct_references_with_snapshot<I: AsCStr>(
         &self,
         id: I,
         snapshot: &TypeArchiveSnapshotId,
     ) -> Array<BnString> {
-        let id = id.into_bytes_with_nul();
         let mut count = 0;
         let result = unsafe {
             BNGetTypeArchiveIncomingDirectTypeReferences(
                 self.handle.as_ptr(),
-                id.as_ref().as_ptr() as *const c_char,
+                id.as_cstr().as_ptr(),
                 snapshot.0.as_ptr() as *const c_char,
                 &mut count,
             )
@@ -568,7 +545,7 @@ impl TypeArchive {
     /// Get all types that reference a given type, and all types that reference them, recursively
     ///
     /// * `id` - Target type id
-    pub fn get_incoming_recursive_references<I: BnStrCompatible>(&self, id: I) -> Array<BnString> {
+    pub fn get_incoming_recursive_references<I: AsCStr>(&self, id: I) -> Array<BnString> {
         self.get_incoming_recursive_references_with_snapshot(id, &TypeArchiveSnapshotId::unset())
     }
 
@@ -576,17 +553,16 @@ impl TypeArchive {
     ///
     /// * `id` - Target type id
     /// * `snapshot` - Snapshot id to search for types, or empty string to search the latest snapshot
-    pub fn get_incoming_recursive_references_with_snapshot<I: BnStrCompatible>(
+    pub fn get_incoming_recursive_references_with_snapshot<I: AsCStr>(
         &self,
         id: I,
         snapshot: &TypeArchiveSnapshotId,
     ) -> Array<BnString> {
-        let id = id.into_bytes_with_nul();
         let mut count = 0;
         let result = unsafe {
             BNGetTypeArchiveIncomingRecursiveTypeReferences(
                 self.handle.as_ptr(),
-                id.as_ref().as_ptr() as *const c_char,
+                id.as_cstr().as_ptr(),
                 snapshot.0.as_ptr() as *const c_char,
                 &mut count,
             )
@@ -596,11 +572,9 @@ impl TypeArchive {
     }
 
     /// Look up a metadata entry in the archive
-    pub fn query_metadata<S: BnStrCompatible>(&self, key: S) -> Option<Ref<Metadata>> {
-        let key = key.into_bytes_with_nul();
-        let result = unsafe {
-            BNTypeArchiveQueryMetadata(self.handle.as_ptr(), key.as_ref().as_ptr() as *const c_char)
-        };
+    pub fn query_metadata<S: AsCStr>(&self, key: S) -> Option<Ref<Metadata>> {
+        let result =
+            unsafe { BNTypeArchiveQueryMetadata(self.handle.as_ptr(), key.as_cstr().as_ptr()) };
         (!result.is_null()).then(|| unsafe { Metadata::ref_from_raw(result) })
     }
 
@@ -608,34 +582,20 @@ impl TypeArchive {
     ///
     /// * `key` - key value to associate the Metadata object with
     /// * `md` - object to store.
-    pub fn store_metadata<S: BnStrCompatible>(&self, key: S, md: &Metadata) {
-        let key = key.into_bytes_with_nul();
+    pub fn store_metadata<S: AsCStr>(&self, key: S, md: &Metadata) {
         let result = unsafe {
-            BNTypeArchiveStoreMetadata(
-                self.handle.as_ptr(),
-                key.as_ref().as_ptr() as *const c_char,
-                md.handle,
-            )
+            BNTypeArchiveStoreMetadata(self.handle.as_ptr(), key.as_cstr().as_ptr(), md.handle)
         };
         assert!(result);
     }
 
     /// Delete a given metadata entry in the archive from the `key`
-    pub fn remove_metadata<S: BnStrCompatible>(&self, key: S) -> bool {
-        let key = key.into_bytes_with_nul();
-        unsafe {
-            BNTypeArchiveRemoveMetadata(
-                self.handle.as_ptr(),
-                key.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn remove_metadata<S: AsCStr>(&self, key: S) -> bool {
+        unsafe { BNTypeArchiveRemoveMetadata(self.handle.as_ptr(), key.as_cstr().as_ptr()) }
     }
 
     /// Turn a given `snapshot` id into a data stream
-    pub fn serialize_snapshot<S: BnStrCompatible>(
-        &self,
-        snapshot: &TypeArchiveSnapshotId,
-    ) -> DataBuffer {
+    pub fn serialize_snapshot(&self, snapshot: &TypeArchiveSnapshotId) -> DataBuffer {
         let result = unsafe {
             BNTypeArchiveSerializeSnapshot(
                 self.handle.as_ptr(),
@@ -707,9 +667,8 @@ impl TypeArchive {
 
     // TODO: Make this AsRef<Path>?
     /// Determine if `file` is a Type Archive
-    pub fn is_type_archive<P: BnStrCompatible>(file: P) -> bool {
-        let file = file.into_bytes_with_nul();
-        unsafe { BNIsTypeArchive(file.as_ref().as_ptr() as *const c_char) }
+    pub fn is_type_archive<P: AsCStr>(file: P) -> bool {
+        unsafe { BNIsTypeArchive(file.as_cstr().as_ptr()) }
     }
 
     ///// Get the TypeContainer interface for this Type Archive, presenting types
@@ -732,7 +691,7 @@ impl TypeArchive {
         parents: &[TypeArchiveSnapshotId],
     ) -> TypeArchiveSnapshotId
     where
-        P: BnStrCompatible,
+        P: AsCStr,
         F: FnMut(&TypeArchiveSnapshotId) -> bool,
     {
         unsafe extern "C" fn cb_callback<F: FnMut(&TypeArchiveSnapshotId) -> bool>(
@@ -779,12 +738,12 @@ impl TypeArchive {
         merge_conflicts: M,
     ) -> Result<BnString, Array<BnString>>
     where
-        B: BnStrCompatible,
-        F: BnStrCompatible,
-        S: BnStrCompatible,
+        B: AsCStr,
+        F: AsCStr,
+        S: AsCStr,
         M: IntoIterator<Item = (MI, MK)>,
-        MI: BnStrCompatible,
-        MK: BnStrCompatible,
+        MI: AsCStr,
+        MK: AsCStr,
     {
         self.merge_snapshots_with_progress(
             base_snapshot,
@@ -814,17 +773,14 @@ impl TypeArchive {
         mut progress: P,
     ) -> Result<BnString, Array<BnString>>
     where
-        B: BnStrCompatible,
-        F: BnStrCompatible,
-        S: BnStrCompatible,
+        B: AsCStr,
+        F: AsCStr,
+        S: AsCStr,
         M: IntoIterator<Item = (MI, MK)>,
-        MI: BnStrCompatible,
-        MK: BnStrCompatible,
+        MI: AsCStr,
+        MK: AsCStr,
         P: ProgressCallback,
     {
-        let base_snapshot = base_snapshot.into_bytes_with_nul();
-        let first_snapshot = first_snapshot.into_bytes_with_nul();
-        let second_snapshot = second_snapshot.into_bytes_with_nul();
         let (merge_keys, merge_values): (Vec<BnString>, Vec<BnString>) = merge_conflicts
             .into_iter()
             .map(|(k, v)| (BnString::new(k), BnString::new(v)))
@@ -841,9 +797,9 @@ impl TypeArchive {
         let success = unsafe {
             BNTypeArchiveMergeSnapshots(
                 self.handle.as_ptr(),
-                base_snapshot.as_ref().as_ptr() as *const c_char,
-                first_snapshot.as_ref().as_ptr() as *const c_char,
-                second_snapshot.as_ref().as_ptr() as *const c_char,
+                base_snapshot.as_cstr().as_ptr(),
+                first_snapshot.as_cstr().as_ptr(),
+                second_snapshot.as_cstr().as_ptr(),
                 merge_keys_raw,
                 merge_values_raw,
                 merge_keys.len(),
@@ -1168,14 +1124,8 @@ impl TypeArchiveMergeConflict {
     }
 
     // TODO: This needs documentation!
-    pub fn success<S: BnStrCompatible>(&self, value: S) -> bool {
-        let value = value.into_bytes_with_nul();
-        unsafe {
-            BNTypeArchiveMergeConflictSuccess(
-                self.handle.as_ptr(),
-                value.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn success<S: AsCStr>(&self, value: S) -> bool {
+        unsafe { BNTypeArchiveMergeConflictSuccess(self.handle.as_ptr(), value.as_cstr().as_ptr()) }
     }
 }
 
diff --git a/rust/src/type_container.rs b/rust/src/type_container.rs
index 947c6912a..a397a04ec 100644
--- a/rust/src/type_container.rs
+++ b/rust/src/type_container.rs
@@ -11,12 +11,12 @@
 use crate::platform::Platform;
 use crate::progress::{NoProgressCallback, ProgressCallback};
 use crate::rc::{Array, Ref};
-use crate::string::{raw_to_string, BnStrCompatible, BnString};
+use crate::string::{raw_to_string, AsCStr, BnString};
 use crate::type_parser::{TypeParserError, TypeParserResult};
 use crate::types::{QualifiedName, QualifiedNameAndType, Type};
 use binaryninjacore_sys::*;
 use std::collections::HashMap;
-use std::ffi::{c_char, c_void};
+use std::ffi::c_void;
 use std::fmt::{Debug, Formatter};
 use std::ptr::NonNull;
 
@@ -137,19 +137,10 @@ impl TypeContainer {
     /// (by id) to use the new name.
     ///
     /// Returns true if the type was renamed.
-    pub fn rename_type<T: Into<QualifiedName>, S: BnStrCompatible>(
-        &self,
-        name: T,
-        type_id: S,
-    ) -> bool {
-        let type_id = type_id.into_bytes_with_nul();
+    pub fn rename_type<T: Into<QualifiedName>, S: AsCStr>(&self, name: T, type_id: S) -> bool {
         let raw_name = QualifiedName::into_raw(name.into());
         let success = unsafe {
-            BNTypeContainerRenameType(
-                self.handle.as_ptr(),
-                type_id.as_ref().as_ptr() as *const c_char,
-                &raw_name,
-            )
+            BNTypeContainerRenameType(self.handle.as_ptr(), type_id.as_cstr().as_ptr(), &raw_name)
         };
         QualifiedName::free_raw(raw_name);
         success
@@ -159,14 +150,8 @@ impl TypeContainer {
     /// not specified and you may end up with broken references if any still exist.
     ///
     /// Returns true if the type was deleted.
-    pub fn delete_type<S: BnStrCompatible>(&self, type_id: S) -> bool {
-        let type_id = type_id.into_bytes_with_nul();
-        unsafe {
-            BNTypeContainerDeleteType(
-                self.handle.as_ptr(),
-                type_id.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn delete_type<S: AsCStr>(&self, type_id: S) -> bool {
+        unsafe { BNTypeContainerDeleteType(self.handle.as_ptr(), type_id.as_cstr().as_ptr()) }
     }
 
     /// Get the unique id of the type in the Type Container with the given name.
@@ -184,13 +169,12 @@ impl TypeContainer {
     /// Get the unique name of the type in the Type Container with the given id.
     ///
     /// If no type with that id exists, returns None.
-    pub fn type_name<S: BnStrCompatible>(&self, type_id: S) -> Option<QualifiedName> {
-        let type_id = type_id.into_bytes_with_nul();
+    pub fn type_name<S: AsCStr>(&self, type_id: S) -> Option<QualifiedName> {
         let mut result = BNQualifiedName::default();
         let success = unsafe {
             BNTypeContainerGetTypeName(
                 self.handle.as_ptr(),
-                type_id.as_ref().as_ptr() as *const c_char,
+                type_id.as_cstr().as_ptr(),
                 &mut result,
             )
         };
@@ -200,13 +184,12 @@ impl TypeContainer {
     /// Get the definition of the type in the Type Container with the given id.
     ///
     /// If no type with that id exists, returns None.
-    pub fn type_by_id<S: BnStrCompatible>(&self, type_id: S) -> Option<Ref<Type>> {
-        let type_id = type_id.into_bytes_with_nul();
+    pub fn type_by_id<S: AsCStr>(&self, type_id: S) -> Option<Ref<Type>> {
         let mut result = std::ptr::null_mut();
         let success = unsafe {
             BNTypeContainerGetTypeById(
                 self.handle.as_ptr(),
-                type_id.as_ref().as_ptr() as *const c_char,
+                type_id.as_cstr().as_ptr(),
                 &mut result,
             )
         };
@@ -305,19 +288,18 @@ impl TypeContainer {
     ///
     /// * `source` - Source code to parse
     /// * `import_dependencies` - If Type Library / Type Archive types should be imported during parsing
-    pub fn parse_type_string<S: BnStrCompatible>(
+    pub fn parse_type_string<S: AsCStr>(
         &self,
         source: S,
         import_dependencies: bool,
     ) -> Result<QualifiedNameAndType, Array<TypeParserError>> {
-        let source = source.into_bytes_with_nul();
         let mut result = BNQualifiedNameAndType::default();
         let mut errors = std::ptr::null_mut();
         let mut error_count = 0;
         let success = unsafe {
             BNTypeContainerParseTypeString(
                 self.handle.as_ptr(),
-                source.as_ref().as_ptr() as *const c_char,
+                source.as_cstr().as_ptr(),
                 import_dependencies,
                 &mut result,
                 &mut errors,
@@ -351,46 +333,41 @@ impl TypeContainer {
         import_dependencies: bool,
     ) -> Result<TypeParserResult, Array<TypeParserError>>
     where
-        S: BnStrCompatible,
-        F: BnStrCompatible,
+        S: AsCStr,
+        F: AsCStr,
         O: IntoIterator,
-        O::Item: BnStrCompatible,
+        O::Item: AsCStr,
         D: IntoIterator,
-        D::Item: BnStrCompatible,
-        A: BnStrCompatible,
+        D::Item: AsCStr,
+        A: AsCStr,
     {
-        let source = source.into_bytes_with_nul();
-        let filename = filename.into_bytes_with_nul();
-        let options: Vec<_> = options
-            .into_iter()
-            .map(|o| o.into_bytes_with_nul())
-            .collect();
-        let options_raw: Vec<*const c_char> = options
+        let options = options.into_iter().collect::<Vec<_>>();
+        let options = options.iter().map(|o| o.as_cstr()).collect::<Vec<_>>();
+        let options_raw = options.iter().map(|o| o.as_ptr()).collect::<Vec<_>>();
+
+        let include_directories = include_directories.into_iter().collect::<Vec<_>>();
+        let include_directories = include_directories
             .iter()
-            .map(|o| o.as_ref().as_ptr() as *const c_char)
-            .collect();
-        let include_directories: Vec<_> = include_directories
-            .into_iter()
-            .map(|d| d.into_bytes_with_nul())
-            .collect();
-        let include_directories_raw: Vec<*const c_char> = include_directories
+            .map(|d| d.as_cstr())
+            .collect::<Vec<_>>();
+        let include_directories_raw = include_directories
             .iter()
-            .map(|d| d.as_ref().as_ptr() as *const c_char)
-            .collect();
-        let auto_type_source = auto_type_source.into_bytes_with_nul();
+            .map(|d| d.as_ptr())
+            .collect::<Vec<_>>();
+
         let mut raw_result = BNTypeParserResult::default();
         let mut errors = std::ptr::null_mut();
         let mut error_count = 0;
         let success = unsafe {
             BNTypeContainerParseTypesFromSource(
                 self.handle.as_ptr(),
-                source.as_ref().as_ptr() as *const c_char,
-                filename.as_ref().as_ptr() as *const c_char,
+                source.as_cstr().as_ptr(),
+                filename.as_cstr().as_ptr(),
                 options_raw.as_ptr(),
                 options_raw.len(),
                 include_directories_raw.as_ptr(),
                 include_directories_raw.len(),
-                auto_type_source.as_ref().as_ptr() as *const c_char,
+                auto_type_source.as_cstr().as_ptr(),
                 import_dependencies,
                 &mut raw_result,
                 &mut errors,
diff --git a/rust/src/type_library.rs b/rust/src/type_library.rs
index ee978513e..08acd932c 100644
--- a/rust/src/type_library.rs
+++ b/rust/src/type_library.rs
@@ -1,13 +1,13 @@
 use binaryninjacore_sys::*;
 
-use core::{ffi, mem, ptr};
+use core::{mem, ptr};
 
 use crate::{
     architecture::CoreArchitecture,
     metadata::Metadata,
     platform::Platform,
     rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref},
-    string::{BnStrCompatible, BnString},
+    string::{AsCStr, BnString},
     types::{QualifiedName, QualifiedNameAndType, Type},
 };
 
@@ -42,10 +42,8 @@ impl TypeLibrary {
     }
 
     /// Creates an empty type library object with a random GUID and the provided name.
-    pub fn new<S: BnStrCompatible>(arch: CoreArchitecture, name: S) -> TypeLibrary {
-        let name = name.into_bytes_with_nul();
-        let new_lib =
-            unsafe { BNNewTypeLibrary(arch.handle, name.as_ref().as_ptr() as *const ffi::c_char) };
+    pub fn new<S: AsCStr>(arch: CoreArchitecture, name: S) -> TypeLibrary {
+        let new_lib = unsafe { BNNewTypeLibrary(arch.handle, name.as_cstr().as_ptr()) };
         unsafe { TypeLibrary::from_raw(ptr::NonNull::new(new_lib).unwrap()) }
     }
 
@@ -57,49 +55,31 @@ impl TypeLibrary {
     }
 
     /// Decompresses a type library file to a file on disk.
-    pub fn decompress_to_file<P: BnStrCompatible, O: BnStrCompatible>(path: P, output: O) -> bool {
-        let path = path.into_bytes_with_nul();
-        let output = output.into_bytes_with_nul();
-        unsafe {
-            BNTypeLibraryDecompressToFile(
-                path.as_ref().as_ptr() as *const ffi::c_char,
-                output.as_ref().as_ptr() as *const ffi::c_char,
-            )
-        }
+    pub fn decompress_to_file<P: AsCStr, O: AsCStr>(path: P, output: O) -> bool {
+        unsafe { BNTypeLibraryDecompressToFile(path.as_cstr().as_ptr(), output.as_cstr().as_ptr()) }
     }
 
     /// Loads a finalized type library instance from file
-    pub fn load_from_file<S: BnStrCompatible>(path: S) -> Option<TypeLibrary> {
-        let path = path.into_bytes_with_nul();
-        let handle =
-            unsafe { BNLoadTypeLibraryFromFile(path.as_ref().as_ptr() as *const ffi::c_char) };
+    pub fn load_from_file<S: AsCStr>(path: S) -> Option<TypeLibrary> {
+        let handle = unsafe { BNLoadTypeLibraryFromFile(path.as_cstr().as_ptr()) };
         ptr::NonNull::new(handle).map(|h| unsafe { TypeLibrary::from_raw(h) })
     }
 
     /// Saves a finalized type library instance to file
-    pub fn write_to_file<S: BnStrCompatible>(&self, path: S) -> bool {
-        let path = path.into_bytes_with_nul();
-        unsafe {
-            BNWriteTypeLibraryToFile(self.as_raw(), path.as_ref().as_ptr() as *const ffi::c_char)
-        }
+    pub fn write_to_file<S: AsCStr>(&self, path: S) -> bool {
+        unsafe { BNWriteTypeLibraryToFile(self.as_raw(), path.as_cstr().as_ptr()) }
     }
 
     /// Looks up the first type library found with a matching name. Keep in mind that names are not
     /// necessarily unique.
-    pub fn from_name<S: BnStrCompatible>(arch: CoreArchitecture, name: S) -> Option<TypeLibrary> {
-        let name = name.into_bytes_with_nul();
-        let handle = unsafe {
-            BNLookupTypeLibraryByName(arch.handle, name.as_ref().as_ptr() as *const ffi::c_char)
-        };
+    pub fn from_name<S: AsCStr>(arch: CoreArchitecture, name: S) -> Option<TypeLibrary> {
+        let handle = unsafe { BNLookupTypeLibraryByName(arch.handle, name.as_cstr().as_ptr()) };
         ptr::NonNull::new(handle).map(|h| unsafe { TypeLibrary::from_raw(h) })
     }
 
     /// Attempts to grab a type library associated with the provided Architecture and GUID pair
-    pub fn from_guid<S: BnStrCompatible>(arch: CoreArchitecture, guid: S) -> Option<TypeLibrary> {
-        let guid = guid.into_bytes_with_nul();
-        let handle = unsafe {
-            BNLookupTypeLibraryByGuid(arch.handle, guid.as_ref().as_ptr() as *const ffi::c_char)
-        };
+    pub fn from_guid<S: AsCStr>(arch: CoreArchitecture, guid: S) -> Option<TypeLibrary> {
+        let handle = unsafe { BNLookupTypeLibraryByGuid(arch.handle, guid.as_cstr().as_ptr()) };
         ptr::NonNull::new(handle).map(|h| unsafe { TypeLibrary::from_raw(h) })
     }
 
@@ -117,11 +97,8 @@ impl TypeLibrary {
     }
 
     /// Sets the name of a type library instance that has not been finalized
-    pub fn set_name<S: BnStrCompatible>(&self, value: S) {
-        let value = value.into_bytes_with_nul();
-        unsafe {
-            BNSetTypeLibraryName(self.as_raw(), value.as_ref().as_ptr() as *const ffi::c_char)
-        }
+    pub fn set_name<S: AsCStr>(&self, value: S) {
+        unsafe { BNSetTypeLibraryName(self.as_raw(), value.as_cstr().as_ptr()) }
     }
 
     /// The `dependency_name` of a library is the name used to record dependencies across
@@ -135,14 +112,8 @@ impl TypeLibrary {
     }
 
     /// Sets the dependency name of a type library instance that has not been finalized
-    pub fn set_dependency_name<S: BnStrCompatible>(&self, value: S) {
-        let value = value.into_bytes_with_nul();
-        unsafe {
-            BNSetTypeLibraryDependencyName(
-                self.as_raw(),
-                value.as_ref().as_ptr() as *const ffi::c_char,
-            )
-        }
+    pub fn set_dependency_name<S: AsCStr>(&self, value: S) {
+        unsafe { BNSetTypeLibraryDependencyName(self.as_raw(), value.as_cstr().as_ptr()) }
     }
 
     /// Returns the GUID associated with the type library
@@ -152,11 +123,8 @@ impl TypeLibrary {
     }
 
     /// Sets the GUID of a type library instance that has not been finalized
-    pub fn set_guid<S: BnStrCompatible>(&self, value: S) {
-        let value = value.into_bytes_with_nul();
-        unsafe {
-            BNSetTypeLibraryGuid(self.as_raw(), value.as_ref().as_ptr() as *const ffi::c_char)
-        }
+    pub fn set_guid<S: AsCStr>(&self, value: S) {
+        unsafe { BNSetTypeLibraryGuid(self.as_raw(), value.as_cstr().as_ptr()) }
     }
 
     /// A list of extra names that will be considered a match by [Platform::get_type_libraries_by_name]
@@ -168,14 +136,8 @@ impl TypeLibrary {
     }
 
     /// Adds an extra name to this type library used during library lookups and dependency resolution
-    pub fn add_alternate_name<S: BnStrCompatible>(&self, value: S) {
-        let value = value.into_bytes_with_nul();
-        unsafe {
-            BNAddTypeLibraryAlternateName(
-                self.as_raw(),
-                value.as_ref().as_ptr() as *const ffi::c_char,
-            )
-        }
+    pub fn add_alternate_name<S: AsCStr>(&self, value: S) {
+        unsafe { BNAddTypeLibraryAlternateName(self.as_raw(), value.as_cstr().as_ptr()) }
     }
 
     /// Returns a list of all platform names that this type library will register with during platform
@@ -212,11 +174,8 @@ impl TypeLibrary {
     }
 
     /// Retrieves a metadata associated with the given key stored in the type library
-    pub fn query_metadata<S: BnStrCompatible>(&self, key: S) -> Option<Metadata> {
-        let key = key.into_bytes_with_nul();
-        let result = unsafe {
-            BNTypeLibraryQueryMetadata(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char)
-        };
+    pub fn query_metadata<S: AsCStr>(&self, key: S) -> Option<Metadata> {
+        let result = unsafe { BNTypeLibraryQueryMetadata(self.as_raw(), key.as_cstr().as_ptr()) };
         (!result.is_null()).then(|| unsafe { Metadata::from_raw(result) })
     }
 
@@ -231,23 +190,13 @@ impl TypeLibrary {
     ///
     /// * `key` - key value to associate the Metadata object with
     /// * `md` - object to store.
-    pub fn store_metadata<S: BnStrCompatible>(&self, key: S, md: &Metadata) {
-        let key = key.into_bytes_with_nul();
-        unsafe {
-            BNTypeLibraryStoreMetadata(
-                self.as_raw(),
-                key.as_ref().as_ptr() as *const ffi::c_char,
-                md.handle,
-            )
-        }
+    pub fn store_metadata<S: AsCStr>(&self, key: S, md: &Metadata) {
+        unsafe { BNTypeLibraryStoreMetadata(self.as_raw(), key.as_cstr().as_ptr(), md.handle) }
     }
 
     /// Removes the metadata associated with key from the current type library.
-    pub fn remove_metadata<S: BnStrCompatible>(&self, key: S) {
-        let key = key.into_bytes_with_nul();
-        unsafe {
-            BNTypeLibraryRemoveMetadata(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char)
-        }
+    pub fn remove_metadata<S: AsCStr>(&self, key: S) {
+        unsafe { BNTypeLibraryRemoveMetadata(self.as_raw(), key.as_cstr().as_ptr()) }
     }
 
     /// Retrieves the metadata associated with the current type library.
@@ -299,15 +248,10 @@ impl TypeLibrary {
     /// Use this api with extreme caution.
     ///
     /// </div>
-    pub fn add_type_source<S: BnStrCompatible>(&self, name: QualifiedName, source: S) {
-        let source = source.into_bytes_with_nul();
+    pub fn add_type_source<S: AsCStr>(&self, name: QualifiedName, source: S) {
         let mut raw_name = QualifiedName::into_raw(name);
         unsafe {
-            BNAddTypeLibraryNamedTypeSource(
-                self.as_raw(),
-                &mut raw_name,
-                source.as_ref().as_ptr() as *const ffi::c_char,
-            )
+            BNAddTypeLibraryNamedTypeSource(self.as_raw(), &mut raw_name, source.as_cstr().as_ptr())
         }
         QualifiedName::free_raw(raw_name);
     }
diff --git a/rust/src/type_parser.rs b/rust/src/type_parser.rs
index 656f0c9d4..259c667a5 100644
--- a/rust/src/type_parser.rs
+++ b/rust/src/type_parser.rs
@@ -6,7 +6,7 @@ use std::ptr::NonNull;
 
 use crate::platform::Platform;
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref};
-use crate::string::{raw_to_string, BnStrCompatible, BnString};
+use crate::string::{raw_to_string, AsCStr, BnString};
 use crate::type_container::TypeContainer;
 use crate::types::{QualifiedName, QualifiedNameAndType, Type};
 
@@ -14,7 +14,7 @@ pub type TypeParserErrorSeverity = BNTypeParserErrorSeverity;
 pub type TypeParserOption = BNTypeParserOption;
 
 /// Register a custom parser with the API
-pub fn register_type_parser<S: BnStrCompatible, T: TypeParser>(
+pub fn register_type_parser<S: AsCStr, T: TypeParser>(
     name: S,
     parser: T,
 ) -> (&'static mut T, CoreTypeParser) {
@@ -29,12 +29,7 @@ pub fn register_type_parser<S: BnStrCompatible, T: TypeParser>(
         freeResult: Some(cb_free_result),
         freeErrorList: Some(cb_free_error_list),
     };
-    let result = unsafe {
-        BNRegisterTypeParser(
-            name.into_bytes_with_nul().as_ref().as_ptr() as *const _,
-            &mut callback,
-        )
-    };
+    let result = unsafe { BNRegisterTypeParser(name.as_cstr().as_ptr(), &mut callback) };
     let core = unsafe { CoreTypeParser::from_raw(NonNull::new(result).unwrap()) };
     (parser, core)
 }
@@ -55,9 +50,8 @@ impl CoreTypeParser {
         unsafe { Array::new(result, count, ()) }
     }
 
-    pub fn parser_by_name<S: BnStrCompatible>(name: S) -> Option<CoreTypeParser> {
-        let name_raw = name.into_bytes_with_nul();
-        let result = unsafe { BNGetTypeParserByName(name_raw.as_ref().as_ptr() as *const c_char) };
+    pub fn parser_by_name<S: AsCStr>(name: S) -> Option<CoreTypeParser> {
+        let result = unsafe { BNGetTypeParserByName(name.as_cstr().as_ptr()) };
         NonNull::new(result).map(|x| unsafe { Self::from_raw(x) })
     }
 
@@ -71,18 +65,17 @@ impl CoreTypeParser {
 impl TypeParser for CoreTypeParser {
     fn get_option_text(&self, option: TypeParserOption, value: &str) -> Option<String> {
         let mut output = std::ptr::null_mut();
-        let value_cstr = BnString::new(value);
         let result = unsafe {
             BNGetTypeParserOptionText(
                 self.handle.as_ptr(),
                 option,
-                value_cstr.as_ptr(),
+                value.as_cstr().as_ptr(),
                 &mut output,
             )
         };
         result.then(|| {
             assert!(!output.is_null());
-            value_cstr.to_string()
+            value.to_string()
         })
     }
 
@@ -95,16 +88,14 @@ impl TypeParser for CoreTypeParser {
         options: &[String],
         include_dirs: &[String],
     ) -> Result<String, Vec<TypeParserError>> {
-        let source_cstr = BnString::new(source);
-        let file_name_cstr = BnString::new(file_name);
         let mut result = std::ptr::null_mut();
         let mut errors = std::ptr::null_mut();
         let mut error_count = 0;
         let success = unsafe {
             BNTypeParserPreprocessSource(
                 self.handle.as_ptr(),
-                source_cstr.as_ptr(),
-                file_name_cstr.as_ptr(),
+                source.as_cstr().as_ptr(),
+                file_name.as_cstr().as_ptr(),
                 platform.handle,
                 existing_types.handle.as_ptr(),
                 options.as_ptr() as *const *const c_char,
@@ -136,24 +127,21 @@ impl TypeParser for CoreTypeParser {
         include_dirs: &[String],
         auto_type_source: &str,
     ) -> Result<TypeParserResult, Vec<TypeParserError>> {
-        let source_cstr = BnString::new(source);
-        let file_name_cstr = BnString::new(file_name);
-        let auto_type_source = BnString::new(auto_type_source);
         let mut raw_result = BNTypeParserResult::default();
         let mut errors = std::ptr::null_mut();
         let mut error_count = 0;
         let success = unsafe {
             BNTypeParserParseTypesFromSource(
                 self.handle.as_ptr(),
-                source_cstr.as_ptr(),
-                file_name_cstr.as_ptr(),
+                source.as_cstr().as_ptr(),
+                file_name.as_cstr().as_ptr(),
                 platform.handle,
                 existing_types.handle.as_ptr(),
                 options.as_ptr() as *const *const c_char,
                 options.len(),
                 include_dirs.as_ptr() as *const *const c_char,
                 include_dirs.len(),
-                auto_type_source.as_ptr(),
+                auto_type_source.as_cstr().as_ptr(),
                 &mut raw_result,
                 &mut errors,
                 &mut error_count,
@@ -176,14 +164,13 @@ impl TypeParser for CoreTypeParser {
         platform: &Platform,
         existing_types: &TypeContainer,
     ) -> Result<QualifiedNameAndType, Vec<TypeParserError>> {
-        let source_cstr = BnString::new(source);
         let mut output = BNQualifiedNameAndType::default();
         let mut errors = std::ptr::null_mut();
         let mut error_count = 0;
         let result = unsafe {
             BNTypeParserParseTypeString(
                 self.handle.as_ptr(),
-                source_cstr.as_ptr(),
+                source.as_cstr().as_ptr(),
                 platform.handle,
                 existing_types.handle.as_ptr(),
                 &mut output,
diff --git a/rust/src/type_printer.rs b/rust/src/type_printer.rs
index 12c716458..7a3d513f6 100644
--- a/rust/src/type_printer.rs
+++ b/rust/src/type_printer.rs
@@ -4,7 +4,7 @@ use crate::binary_view::BinaryView;
 use crate::disassembly::InstructionTextToken;
 use crate::platform::Platform;
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref};
-use crate::string::{raw_to_string, BnStrCompatible, BnString};
+use crate::string::{raw_to_string, AsCStr, BnString};
 use crate::type_container::TypeContainer;
 use crate::types::{NamedTypeReference, QualifiedName, QualifiedNameAndType, Type};
 use binaryninjacore_sys::*;
@@ -15,7 +15,7 @@ pub type TokenEscapingType = BNTokenEscapingType;
 pub type TypeDefinitionLineType = BNTypeDefinitionLineType;
 
 /// Register a custom parser with the API
-pub fn register_type_printer<S: BnStrCompatible, T: TypePrinter>(
+pub fn register_type_printer<S: AsCStr, T: TypePrinter>(
     name: S,
     parser: T,
 ) -> (&'static mut T, CoreTypePrinter) {
@@ -34,12 +34,7 @@ pub fn register_type_printer<S: BnStrCompatible, T: TypePrinter>(
         freeString: Some(cb_free_string),
         freeLines: Some(cb_free_lines),
     };
-    let result = unsafe {
-        BNRegisterTypePrinter(
-            name.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
-            &mut callback,
-        )
-    };
+    let result = unsafe { BNRegisterTypePrinter(name.as_cstr().as_ptr(), &mut callback) };
     let core = unsafe { CoreTypePrinter::from_raw(NonNull::new(result).unwrap()) };
     (parser, core)
 }
@@ -61,9 +56,8 @@ impl CoreTypePrinter {
         unsafe { Array::new(result, count, ()) }
     }
 
-    pub fn printer_by_name<S: BnStrCompatible>(name: S) -> Option<CoreTypePrinter> {
-        let name_raw = name.into_bytes_with_nul();
-        let result = unsafe { BNGetTypePrinterByName(name_raw.as_ref().as_ptr() as *const c_char) };
+    pub fn printer_by_name<S: AsCStr>(name: S) -> Option<CoreTypePrinter> {
+        let result = unsafe { BNGetTypePrinterByName(name.as_cstr().as_ptr()) };
         NonNull::new(result).map(|x| unsafe { Self::from_raw(x) })
     }
 
diff --git a/rust/src/types.rs b/rust/src/types.rs
index 9d1353bd9..4238ee77a 100644
--- a/rust/src/types.rs
+++ b/rust/src/types.rs
@@ -24,7 +24,7 @@ use crate::{
     binary_view::{BinaryView, BinaryViewExt},
     calling_convention::CoreCallingConvention,
     rc::*,
-    string::{BnStrCompatible, BnString},
+    string::{AsCStr, BnString},
 };
 
 use crate::confidence::{Conf, MAX_CONFIDENCE, MIN_CONFIDENCE};
@@ -253,44 +253,29 @@ impl TypeBuilder {
             Self::from_raw(BNCreateIntegerTypeBuilder(
                 width,
                 &mut is_signed,
-                BnString::new("").as_ptr() as *mut _,
+                BnString::new("").as_ptr(),
             ))
         }
     }
 
-    pub fn named_int<S: BnStrCompatible>(width: usize, is_signed: bool, alt_name: S) -> Self {
+    pub fn named_int<S: AsCStr>(width: usize, is_signed: bool, alt_name: S) -> Self {
         let mut is_signed = Conf::new(is_signed, MAX_CONFIDENCE).into();
-        // let alt_name = BnString::new(alt_name);
-        let alt_name = alt_name.into_bytes_with_nul(); // This segfaulted once, so the above version is there if we need to change to it, but in theory this is copied into a `const string&` on the C++ side; I'm just not 100% confident that a constant reference copies data
 
         unsafe {
             Self::from_raw(BNCreateIntegerTypeBuilder(
                 width,
                 &mut is_signed,
-                alt_name.as_ref().as_ptr() as _,
+                alt_name.as_cstr().as_ptr(),
             ))
         }
     }
 
     pub fn float(width: usize) -> Self {
-        unsafe {
-            Self::from_raw(BNCreateFloatTypeBuilder(
-                width,
-                BnString::new("").as_ptr() as *mut _,
-            ))
-        }
+        unsafe { Self::from_raw(BNCreateFloatTypeBuilder(width, BnString::new("").as_ptr())) }
     }
 
-    pub fn named_float<S: BnStrCompatible>(width: usize, alt_name: S) -> Self {
-        // let alt_name = BnString::new(alt_name);
-        let alt_name = alt_name.into_bytes_with_nul(); // See same line in `named_int` above
-
-        unsafe {
-            Self::from_raw(BNCreateFloatTypeBuilder(
-                width,
-                alt_name.as_ref().as_ptr() as _,
-            ))
-        }
+    pub fn named_float<S: AsCStr>(width: usize, alt_name: S) -> Self {
+        unsafe { Self::from_raw(BNCreateFloatTypeBuilder(width, alt_name.as_cstr().as_ptr())) }
     }
 
     pub fn array<'a, T: Into<Conf<&'a Type>>>(ty: T, count: u64) -> Self {
@@ -630,12 +615,7 @@ impl Type {
     }
 
     pub fn wide_char(width: usize) -> Ref<Self> {
-        unsafe {
-            Self::ref_from_raw(BNCreateWideCharType(
-                width,
-                BnString::new("").as_ptr() as *mut _,
-            ))
-        }
+        unsafe { Self::ref_from_raw(BNCreateWideCharType(width, BnString::new("").as_ptr())) }
     }
 
     pub fn int(width: usize, is_signed: bool) -> Ref<Self> {
@@ -644,39 +624,29 @@ impl Type {
             Self::ref_from_raw(BNCreateIntegerType(
                 width,
                 &mut is_signed,
-                BnString::new("").as_ptr() as *mut _,
+                BnString::new("").as_ptr(),
             ))
         }
     }
 
-    pub fn named_int<S: BnStrCompatible>(width: usize, is_signed: bool, alt_name: S) -> Ref<Self> {
+    pub fn named_int<S: AsCStr>(width: usize, is_signed: bool, alt_name: S) -> Ref<Self> {
         let mut is_signed = Conf::new(is_signed, MAX_CONFIDENCE).into();
-        // let alt_name = BnString::new(alt_name);
-        let alt_name = alt_name.into_bytes_with_nul(); // This segfaulted once, so the above version is there if we need to change to it, but in theory this is copied into a `const string&` on the C++ side; I'm just not 100% confident that a constant reference copies data
 
         unsafe {
             Self::ref_from_raw(BNCreateIntegerType(
                 width,
                 &mut is_signed,
-                alt_name.as_ref().as_ptr() as _,
+                alt_name.as_cstr().as_ptr(),
             ))
         }
     }
 
     pub fn float(width: usize) -> Ref<Self> {
-        unsafe {
-            Self::ref_from_raw(BNCreateFloatType(
-                width,
-                BnString::new("").as_ptr() as *mut _,
-            ))
-        }
+        unsafe { Self::ref_from_raw(BNCreateFloatType(width, BnString::new("").as_ptr())) }
     }
 
-    pub fn named_float<S: BnStrCompatible>(width: usize, alt_name: S) -> Ref<Self> {
-        // let alt_name = BnString::new(alt_name);
-        let alt_name = alt_name.into_bytes_with_nul(); // See same line in `named_int` above
-
-        unsafe { Self::ref_from_raw(BNCreateFloatType(width, alt_name.as_ref().as_ptr() as _)) }
+    pub fn named_float<S: AsCStr>(width: usize, alt_name: S) -> Ref<Self> {
+        unsafe { Self::ref_from_raw(BNCreateFloatType(width, alt_name.as_cstr().as_ptr())) }
     }
 
     pub fn array<'a, T: Into<Conf<&'a Type>>>(ty: T, count: u64) -> Ref<Self> {
@@ -1216,26 +1186,21 @@ impl EnumerationBuilder {
         unsafe { Enumeration::ref_from_raw(BNFinalizeEnumerationBuilder(self.handle)) }
     }
 
-    pub fn append<S: BnStrCompatible>(&mut self, name: S) -> &mut Self {
-        let name = name.into_bytes_with_nul();
-        unsafe {
-            BNAddEnumerationBuilderMember(self.handle, name.as_ref().as_ptr() as _);
-        }
+    pub fn append<S: AsCStr>(&mut self, name: S) -> &mut Self {
+        unsafe { BNAddEnumerationBuilderMember(self.handle, name.as_cstr().as_ptr()) }
         self
     }
 
-    pub fn insert<S: BnStrCompatible>(&mut self, name: S, value: u64) -> &mut Self {
-        let name = name.into_bytes_with_nul();
+    pub fn insert<S: AsCStr>(&mut self, name: S, value: u64) -> &mut Self {
         unsafe {
-            BNAddEnumerationBuilderMemberWithValue(self.handle, name.as_ref().as_ptr() as _, value);
+            BNAddEnumerationBuilderMemberWithValue(self.handle, name.as_cstr().as_ptr(), value)
         }
         self
     }
 
-    pub fn replace<S: BnStrCompatible>(&mut self, id: usize, name: S, value: u64) -> &mut Self {
-        let name = name.into_bytes_with_nul();
+    pub fn replace<S: AsCStr>(&mut self, id: usize, name: S, value: u64) -> &mut Self {
         unsafe {
-            BNReplaceEnumerationBuilderMember(self.handle, id, name.as_ref().as_ptr() as _, value);
+            BNReplaceEnumerationBuilderMember(self.handle, id, name.as_cstr().as_ptr(), value)
         }
         self
     }
@@ -1475,20 +1440,19 @@ impl StructureBuilder {
         self
     }
 
-    pub fn append<'a, S: BnStrCompatible, T: Into<Conf<&'a Type>>>(
+    pub fn append<'a, S: AsCStr, T: Into<Conf<&'a Type>>>(
         &mut self,
         ty: T,
         name: S,
         access: MemberAccess,
         scope: MemberScope,
     ) -> &mut Self {
-        let name = name.into_bytes_with_nul();
         let owned_raw_ty = Conf::<&Type>::into_raw(ty.into());
         unsafe {
             BNAddStructureBuilderMember(
                 self.handle,
                 &owned_raw_ty,
-                name.as_ref().as_ptr() as _,
+                name.as_cstr().as_ptr(),
                 access,
                 scope,
             );
@@ -1512,7 +1476,7 @@ impl StructureBuilder {
         self
     }
 
-    pub fn insert<'a, S: BnStrCompatible, T: Into<Conf<&'a Type>>>(
+    pub fn insert<'a, S: AsCStr, T: Into<Conf<&'a Type>>>(
         &mut self,
         ty: T,
         name: S,
@@ -1521,13 +1485,12 @@ impl StructureBuilder {
         access: MemberAccess,
         scope: MemberScope,
     ) -> &mut Self {
-        let name = name.into_bytes_with_nul();
         let owned_raw_ty = Conf::<&Type>::into_raw(ty.into());
         unsafe {
             BNAddStructureBuilderMemberAtOffset(
                 self.handle,
                 &owned_raw_ty,
-                name.as_ref().as_ptr() as _,
+                name.as_cstr().as_ptr(),
                 offset,
                 overwrite_existing,
                 access,
@@ -1537,21 +1500,20 @@ impl StructureBuilder {
         self
     }
 
-    pub fn replace<'a, S: BnStrCompatible, T: Into<Conf<&'a Type>>>(
+    pub fn replace<'a, S: AsCStr, T: Into<Conf<&'a Type>>>(
         &mut self,
         index: usize,
         ty: T,
         name: S,
         overwrite_existing: bool,
     ) -> &mut Self {
-        let name = name.into_bytes_with_nul();
         let owned_raw_ty = Conf::<&Type>::into_raw(ty.into());
         unsafe {
             BNReplaceStructureBuilderMember(
                 self.handle,
                 index,
                 &owned_raw_ty,
-                name.as_ref().as_ptr() as _,
+                name.as_cstr().as_ptr(),
                 overwrite_existing,
             )
         }
@@ -1869,17 +1831,16 @@ impl NamedTypeReference {
     /// You should not assign type ids yourself: if you use this to reference a type you are going
     /// to create but have not yet created, you may run into problems when giving your types to
     /// a BinaryView.
-    pub fn new_with_id<T: Into<QualifiedName>, S: BnStrCompatible>(
+    pub fn new_with_id<T: Into<QualifiedName>, S: AsCStr>(
         type_class: NamedTypeReferenceClass,
         type_id: S,
         name: T,
     ) -> Ref<Self> {
-        let type_id = type_id.into_bytes_with_nul();
         let mut raw_name = QualifiedName::into_raw(name.into());
         let result = unsafe {
             Self::ref_from_raw(BNCreateNamedType(
                 type_class,
-                type_id.as_ref().as_ptr() as _,
+                type_id.as_cstr().as_ptr(),
                 &mut raw_name,
             ))
         };
diff --git a/rust/src/websocket/client.rs b/rust/src/websocket/client.rs
index 36c7bedd2..0480e229a 100644
--- a/rust/src/websocket/client.rs
+++ b/rust/src/websocket/client.rs
@@ -1,5 +1,5 @@
 use crate::rc::{Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use binaryninjacore_sys::*;
 use std::ffi::{c_char, c_void, CStr};
 use std::ptr::NonNull;
@@ -21,8 +21,8 @@ pub trait WebsocketClient: Sync + Send {
     fn connect<I, K, V>(&self, host: &str, headers: I) -> bool
     where
         I: IntoIterator<Item = (K, V)>,
-        K: BnStrCompatible,
-        V: BnStrCompatible;
+        K: AsCStr,
+        V: AsCStr;
 
     fn write(&self, data: &[u8]) -> bool;
 
@@ -77,23 +77,17 @@ impl CoreWebsocketClient {
     ) -> bool
     where
         I: IntoIterator<Item = (K, V)>,
-        K: BnStrCompatible,
-        V: BnStrCompatible,
+        K: AsCStr,
+        V: AsCStr,
         C: WebsocketClientCallback,
     {
-        let url = host.into_bytes_with_nul();
-        let (header_keys, header_values): (Vec<K::Result>, Vec<V::Result>) = headers
-            .into_iter()
-            .map(|(k, v)| (k.into_bytes_with_nul(), v.into_bytes_with_nul()))
-            .unzip();
-        let header_keys: Vec<*const c_char> = header_keys
-            .iter()
-            .map(|k| k.as_ref().as_ptr() as *const c_char)
-            .collect();
-        let header_values: Vec<*const c_char> = header_values
+        let headers = headers.into_iter().collect::<Vec<_>>();
+        let (header_keys, header_values): (Vec<_>, Vec<_>) = headers
             .iter()
-            .map(|v| v.as_ref().as_ptr() as *const c_char)
-            .collect();
+            .map(|(k, v)| (k.as_cstr(), v.as_cstr()))
+            .unzip();
+        let header_keys: Vec<_> = header_keys.iter().map(|k| k.as_ptr()).collect();
+        let header_values: Vec<_> = header_values.iter().map(|v| v.as_ptr()).collect();
         // SAFETY: This context will only be live for the duration of BNConnectWebsocketClient
         // SAFETY: Any subsequent call to BNConnectWebsocketClient will write over the context.
         let mut output_callbacks = BNWebsocketClientOutputCallbacks {
@@ -106,7 +100,7 @@ impl CoreWebsocketClient {
         unsafe {
             BNConnectWebsocketClient(
                 self.handle.as_ptr(),
-                url.as_ptr() as *const c_char,
+                host.as_cstr().as_ptr(),
                 header_keys.len().try_into().unwrap(),
                 header_keys.as_ptr(),
                 header_values.as_ptr(),
@@ -129,10 +123,7 @@ impl CoreWebsocketClient {
 
     /// Call the error callback function
     pub fn notify_error(&self, msg: &str) {
-        let error = msg.into_bytes_with_nul();
-        unsafe {
-            BNNotifyWebsocketClientError(self.handle.as_ptr(), error.as_ptr() as *const c_char)
-        }
+        unsafe { BNNotifyWebsocketClientError(self.handle.as_ptr(), msg.as_cstr().as_ptr()) }
     }
 
     /// Call the read callback function, forward the callback returned value
@@ -195,8 +186,8 @@ pub(crate) unsafe extern "C" fn cb_connect<W: WebsocketClient>(
     let header_count = usize::try_from(header_count).unwrap();
     let header_keys = core::slice::from_raw_parts(header_keys as *const BnString, header_count);
     let header_values = core::slice::from_raw_parts(header_values as *const BnString, header_count);
-    let header_keys_str = header_keys.iter().map(|s| s.to_string_lossy());
-    let header_values_str = header_values.iter().map(|s| s.to_string_lossy());
+    let header_keys_str = header_keys.iter().map(|s| s.to_string());
+    let header_values_str = header_values.iter().map(|s| s.to_string());
     let header = header_keys_str.zip(header_values_str);
     ctxt.connect(&host.to_string_lossy(), header)
 }
diff --git a/rust/src/websocket/provider.rs b/rust/src/websocket/provider.rs
index 0e28afe45..a12a50ba1 100644
--- a/rust/src/websocket/provider.rs
+++ b/rust/src/websocket/provider.rs
@@ -1,9 +1,9 @@
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 use crate::websocket::client;
 use crate::websocket::client::{CoreWebsocketClient, WebsocketClient};
 use binaryninjacore_sys::*;
-use std::ffi::{c_char, c_void};
+use std::ffi::c_void;
 use std::mem::MaybeUninit;
 use std::ptr::NonNull;
 
@@ -11,13 +11,12 @@ pub fn register_websocket_provider<W>(name: &str) -> &'static mut W
 where
     W: WebsocketProvider,
 {
-    let name = name.into_bytes_with_nul();
     let provider_uninit = MaybeUninit::uninit();
     // SAFETY: Websocket provider is never freed
     let leaked_provider = Box::leak(Box::new(provider_uninit));
     let result = unsafe {
         BNRegisterWebsocketProvider(
-            name.as_ptr() as *const c_char,
+            name.as_cstr().as_ptr(),
             &mut BNWebsocketProviderCallbacks {
                 context: leaked_provider as *mut _ as *mut c_void,
                 createClient: Some(cb_create_client::<W>),
@@ -80,10 +79,8 @@ impl CoreWebsocketProvider {
         unsafe { Array::new(result, count, ()) }
     }
 
-    pub fn by_name<S: BnStrCompatible>(name: S) -> Option<CoreWebsocketProvider> {
-        let name = name.into_bytes_with_nul();
-        let result =
-            unsafe { BNGetWebsocketProviderByName(name.as_ref().as_ptr() as *const c_char) };
+    pub fn by_name<S: AsCStr>(name: S) -> Option<CoreWebsocketProvider> {
+        let result = unsafe { BNGetWebsocketProviderByName(name.as_cstr().as_ptr()) };
         NonNull::new(result).map(|h| unsafe { Self::from_raw(h) })
     }
 
diff --git a/rust/src/worker_thread.rs b/rust/src/worker_thread.rs
index 349456e5f..91caef17a 100644
--- a/rust/src/worker_thread.rs
+++ b/rust/src/worker_thread.rs
@@ -1,6 +1,6 @@
-use crate::string::BnStrCompatible;
+use crate::string::AsCStr;
 use binaryninjacore_sys::*;
-use std::ffi::{c_char, c_void};
+use std::ffi::c_void;
 
 pub struct WorkerThreadActionExecutor {
     func: Box<dyn Fn()>,
@@ -17,41 +17,38 @@ impl WorkerThreadActionExecutor {
     }
 }
 
-pub fn execute_on_worker_thread<F: Fn() + 'static, S: BnStrCompatible>(name: S, f: F) {
+pub fn execute_on_worker_thread<F: Fn() + 'static, S: AsCStr>(name: S, f: F) {
     let boxed_executor = Box::new(WorkerThreadActionExecutor { func: Box::new(f) });
     let raw_executor = Box::into_raw(boxed_executor);
-    let name = name.into_bytes_with_nul();
     unsafe {
         BNWorkerEnqueueNamed(
             raw_executor as *mut c_void,
             Some(WorkerThreadActionExecutor::cb_execute),
-            name.as_ref().as_ptr() as *const c_char,
+            name.as_cstr().as_ptr(),
         )
     }
 }
 
-pub fn execute_on_worker_thread_priority<F: Fn() + 'static, S: BnStrCompatible>(name: S, f: F) {
+pub fn execute_on_worker_thread_priority<F: Fn() + 'static, S: AsCStr>(name: S, f: F) {
     let boxed_executor = Box::new(WorkerThreadActionExecutor { func: Box::new(f) });
     let raw_executor = Box::into_raw(boxed_executor);
-    let name = name.into_bytes_with_nul();
     unsafe {
         BNWorkerPriorityEnqueueNamed(
             raw_executor as *mut c_void,
             Some(WorkerThreadActionExecutor::cb_execute),
-            name.as_ref().as_ptr() as *const c_char,
+            name.as_cstr().as_ptr(),
         )
     }
 }
 
-pub fn execute_on_worker_thread_interactive<F: Fn() + 'static, S: BnStrCompatible>(name: S, f: F) {
+pub fn execute_on_worker_thread_interactive<F: Fn() + 'static, S: AsCStr>(name: S, f: F) {
     let boxed_executor = Box::new(WorkerThreadActionExecutor { func: Box::new(f) });
     let raw_executor = Box::into_raw(boxed_executor);
-    let name = name.into_bytes_with_nul();
     unsafe {
         BNWorkerInteractiveEnqueueNamed(
             raw_executor as *mut c_void,
             Some(WorkerThreadActionExecutor::cb_execute),
-            name.as_ref().as_ptr() as *const c_char,
+            name.as_cstr().as_ptr(),
         )
     }
 }
diff --git a/rust/src/workflow.rs b/rust/src/workflow.rs
index b9c67a5df..8225288e6 100644
--- a/rust/src/workflow.rs
+++ b/rust/src/workflow.rs
@@ -12,7 +12,7 @@ use crate::low_level_il::function::{LowLevelILFunction, Mutable, NonSSA, NonSSAV
 use crate::low_level_il::MutableLiftedILFunction;
 use crate::medium_level_il::MediumLevelILFunction;
 use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::string::{BnStrCompatible, BnString};
+use crate::string::{AsCStr, BnString};
 
 #[repr(transparent)]
 /// The AnalysisContext struct is used to represent the current state of
@@ -115,14 +115,8 @@ impl AnalysisContext {
         }
     }
 
-    pub fn inform<S: BnStrCompatible>(&self, request: S) -> bool {
-        let request = request.into_bytes_with_nul();
-        unsafe {
-            BNAnalysisContextInform(
-                self.handle.as_ptr(),
-                request.as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn inform<S: AsCStr>(&self, request: S) -> bool {
+        unsafe { BNAnalysisContextInform(self.handle.as_ptr(), request.as_cstr().as_ptr()) }
     }
 
     pub fn set_basic_blocks<I>(&self, blocks: I)
@@ -173,12 +167,11 @@ impl Activity {
         Ref::new(Self { handle })
     }
 
-    pub fn new<S: BnStrCompatible>(config: S) -> Ref<Self> {
+    pub fn new<S: AsCStr>(config: S) -> Ref<Self> {
         unsafe extern "C" fn cb_action_nop(_: *mut c_void, _: *mut BNAnalysisContext) {}
-        let config = config.into_bytes_with_nul();
         let result = unsafe {
             BNCreateActivity(
-                config.as_ref().as_ptr() as *const c_char,
+                config.as_cstr().as_ptr(),
                 std::ptr::null_mut(),
                 Some(cb_action_nop),
             )
@@ -188,7 +181,7 @@ impl Activity {
 
     pub fn new_with_action<S, F>(config: S, mut action: F) -> Ref<Self>
     where
-        S: BnStrCompatible,
+        S: AsCStr,
         F: FnMut(&AnalysisContext),
     {
         unsafe extern "C" fn cb_action<F: FnMut(&AnalysisContext)>(
@@ -200,10 +193,9 @@ impl Activity {
                 ctxt(&AnalysisContext::from_raw(analysis))
             }
         }
-        let config = config.into_bytes_with_nul();
         let result = unsafe {
             BNCreateActivity(
-                config.as_ref().as_ptr() as *const c_char,
+                config.as_cstr().as_ptr(),
                 &mut action as *mut F as *mut c_void,
                 Some(cb_action::<F>),
             )
@@ -257,9 +249,8 @@ impl Workflow {
     /// Create a new unregistered [Workflow] with no activities.
     ///
     /// To get a copy of an existing registered [Workflow] use [Workflow::clone_to].
-    pub fn new<S: BnStrCompatible>(name: S) -> Ref<Self> {
-        let name = name.into_bytes_with_nul();
-        let result = unsafe { BNCreateWorkflow(name.as_ref().as_ptr() as *const c_char) };
+    pub fn new<S: AsCStr>(name: S) -> Ref<Self> {
+        let result = unsafe { BNCreateWorkflow(name.as_cstr().as_ptr()) };
         unsafe { Workflow::ref_from_raw(NonNull::new(result).unwrap()) }
     }
 
@@ -267,7 +258,7 @@ impl Workflow {
     ///
     /// * `name` - the name for the new [Workflow]
     #[must_use]
-    pub fn clone_to<S: BnStrCompatible + Clone>(&self, name: S) -> Ref<Workflow> {
+    pub fn clone_to<S: AsCStr + Clone>(&self, name: S) -> Ref<Workflow> {
         self.clone_to_with_root(name, "")
     }
 
@@ -276,29 +267,25 @@ impl Workflow {
     /// * `name` - the name for the new [Workflow]
     /// * `root_activity` - perform the clone operation with this activity as the root
     #[must_use]
-    pub fn clone_to_with_root<S: BnStrCompatible, A: BnStrCompatible>(
+    pub fn clone_to_with_root<S: AsCStr, A: AsCStr>(
         &self,
         name: S,
         root_activity: A,
     ) -> Ref<Workflow> {
-        let raw_name = name.into_bytes_with_nul();
-        let activity = root_activity.into_bytes_with_nul();
         unsafe {
             Self::ref_from_raw(
                 NonNull::new(BNWorkflowClone(
                     self.handle.as_ptr(),
-                    raw_name.as_ref().as_ptr() as *const c_char,
-                    activity.as_ref().as_ptr() as *const c_char,
+                    name.as_cstr().as_ptr(),
+                    root_activity.as_cstr().as_ptr(),
                 ))
                 .unwrap(),
             )
         }
     }
 
-    pub fn instance<S: BnStrCompatible>(name: S) -> Ref<Workflow> {
-        let result = unsafe {
-            BNWorkflowInstance(name.into_bytes_with_nul().as_ref().as_ptr() as *const c_char)
-        };
+    pub fn instance<S: AsCStr>(name: S) -> Ref<Workflow> {
+        let result = unsafe { BNWorkflowInstance(name.as_cstr().as_ptr()) };
         unsafe { Workflow::ref_from_raw(NonNull::new(result).unwrap()) }
     }
 
@@ -324,14 +311,8 @@ impl Workflow {
     /// Register this [Workflow], making it immutable and available for use.
     ///
     /// * `configuration` - a JSON representation of the workflow configuration
-    pub fn register_with_config<S: BnStrCompatible>(&self, config: S) -> Result<(), ()> {
-        let config = config.into_bytes_with_nul();
-        if unsafe {
-            BNRegisterWorkflow(
-                self.handle.as_ptr(),
-                config.as_ref().as_ptr() as *const c_char,
-            )
-        } {
+    pub fn register_with_config<S: AsCStr>(&self, config: S) -> Result<(), ()> {
+        if unsafe { BNRegisterWorkflow(self.handle.as_ptr(), config.as_cstr().as_ptr()) } {
             Ok(())
         } else {
             Err(())
@@ -356,16 +337,11 @@ impl Workflow {
     ) -> Result<Ref<Activity>, ()>
     where
         I: IntoIterator,
-        I::Item: BnStrCompatible,
+        I::Item: AsCStr,
     {
-        let subactivities_raw: Vec<_> = subactivities
-            .into_iter()
-            .map(|x| x.into_bytes_with_nul())
-            .collect();
-        let mut subactivities_ptr: Vec<*const _> = subactivities_raw
-            .iter()
-            .map(|x| x.as_ref().as_ptr() as *const c_char)
-            .collect();
+        let subactivities = subactivities.into_iter().collect::<Vec<_>>();
+        let subactivities_raw: Vec<_> = subactivities.iter().map(|x| x.as_cstr()).collect();
+        let mut subactivities_ptr: Vec<_> = subactivities_raw.iter().map(|x| x.as_ptr()).collect();
         let result = unsafe {
             BNWorkflowRegisterActivity(
                 self.handle.as_ptr(),
@@ -379,13 +355,8 @@ impl Workflow {
     }
 
     /// Determine if an Activity exists in this [Workflow].
-    pub fn contains<A: BnStrCompatible>(&self, activity: A) -> bool {
-        unsafe {
-            BNWorkflowContains(
-                self.handle.as_ptr(),
-                activity.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn contains<A: AsCStr>(&self, activity: A) -> bool {
+        unsafe { BNWorkflowContains(self.handle.as_ptr(), activity.as_cstr().as_ptr()) }
     }
 
     /// Retrieve the configuration as an adjacency list in JSON for the [Workflow].
@@ -397,12 +368,9 @@ impl Workflow {
     /// [Workflow], just for the given `activity`.
     ///
     /// `activity` - return the configuration for the `activity`
-    pub fn configuration_with_activity<A: BnStrCompatible>(&self, activity: A) -> BnString {
+    pub fn configuration_with_activity<A: AsCStr>(&self, activity: A) -> BnString {
         let result = unsafe {
-            BNWorkflowGetConfiguration(
-                self.handle.as_ptr(),
-                activity.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
-            )
+            BNWorkflowGetConfiguration(self.handle.as_ptr(), activity.as_cstr().as_ptr())
         };
         assert!(!result.is_null());
         unsafe { BnString::from_raw(result) }
@@ -418,14 +386,9 @@ impl Workflow {
     }
 
     /// Retrieve the Activity object for the specified `name`.
-    pub fn activity<A: BnStrCompatible>(&self, name: A) -> Option<Ref<Activity>> {
-        let name = name.into_bytes_with_nul();
-        let result = unsafe {
-            BNWorkflowGetActivity(
-                self.handle.as_ptr(),
-                name.as_ref().as_ptr() as *const c_char,
-            )
-        };
+    pub fn activity<A: AsCStr>(&self, name: A) -> Option<Ref<Activity>> {
+        let result =
+            unsafe { BNWorkflowGetActivity(self.handle.as_ptr(), name.as_cstr().as_ptr()) };
         NonNull::new(result).map(|a| unsafe { Activity::ref_from_raw(a) })
     }
 
@@ -433,12 +396,12 @@ impl Workflow {
     /// specified just for the given `activity`.
     ///
     /// * `activity` - if specified, return the roots for the `activity`
-    pub fn activity_roots<A: BnStrCompatible>(&self, activity: A) -> Array<BnString> {
+    pub fn activity_roots<A: AsCStr>(&self, activity: A) -> Array<BnString> {
         let mut count = 0;
         let result = unsafe {
             BNWorkflowGetActivityRoots(
                 self.handle.as_ptr(),
-                activity.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
+                activity.as_cstr().as_ptr(),
                 &mut count,
             )
         };
@@ -450,16 +413,12 @@ impl Workflow {
     ///
     /// * `activity` - if specified, return the direct children and optionally the descendants of the `activity` (includes `activity`)
     /// * `immediate` - whether to include only direct children of `activity` or all descendants
-    pub fn subactivities<A: BnStrCompatible>(
-        &self,
-        activity: A,
-        immediate: bool,
-    ) -> Array<BnString> {
+    pub fn subactivities<A: AsCStr>(&self, activity: A, immediate: bool) -> Array<BnString> {
         let mut count = 0;
         let result = unsafe {
             BNWorkflowGetSubactivities(
                 self.handle.as_ptr(),
-                activity.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
+                activity.as_cstr().as_ptr(),
                 immediate,
                 &mut count,
             )
@@ -474,22 +433,17 @@ impl Workflow {
     /// * `activities` - the list of Activities to assign
     pub fn assign_subactivities<A, I>(&self, activity: A, activities: I) -> bool
     where
-        A: BnStrCompatible,
+        A: AsCStr,
         I: IntoIterator,
-        I::Item: BnStrCompatible,
+        I::Item: AsCStr,
     {
-        let input_list: Vec<_> = activities
-            .into_iter()
-            .map(|a| a.into_bytes_with_nul())
-            .collect();
-        let mut input_list_ptr: Vec<*const _> = input_list
-            .iter()
-            .map(|x| x.as_ref().as_ptr() as *const c_char)
-            .collect();
+        let activities = activities.into_iter().collect::<Vec<_>>();
+        let input_list: Vec<_> = activities.iter().map(|a| a.as_cstr()).collect();
+        let mut input_list_ptr: Vec<_> = input_list.iter().map(|x| x.as_ptr()).collect();
         unsafe {
             BNWorkflowAssignSubactivities(
                 self.handle.as_ptr(),
-                activity.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
+                activity.as_cstr().as_ptr(),
                 input_list_ptr.as_mut_ptr(),
                 input_list.len(),
             )
@@ -507,22 +461,17 @@ impl Workflow {
     /// * `activities` - the list of Activities to insert
     pub fn insert<A, I>(&self, activity: A, activities: I) -> bool
     where
-        A: BnStrCompatible,
+        A: AsCStr,
         I: IntoIterator,
-        I::Item: BnStrCompatible,
+        I::Item: AsCStr,
     {
-        let input_list: Vec<_> = activities
-            .into_iter()
-            .map(|a| a.into_bytes_with_nul())
-            .collect();
-        let mut input_list_ptr: Vec<*const _> = input_list
-            .iter()
-            .map(|x| x.as_ref().as_ptr() as *const c_char)
-            .collect();
+        let activities = activities.into_iter().collect::<Vec<_>>();
+        let input_list: Vec<_> = activities.iter().map(|a| a.as_cstr()).collect();
+        let mut input_list_ptr: Vec<*const _> = input_list.iter().map(|x| x.as_ptr()).collect();
         unsafe {
             BNWorkflowInsert(
                 self.handle.as_ptr(),
-                activity.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
+                activity.as_cstr().as_ptr(),
                 input_list_ptr.as_mut_ptr(),
                 input_list.len(),
             )
@@ -535,22 +484,17 @@ impl Workflow {
     /// * `activities` - the list of Activities to insert
     pub fn insert_after<A, I>(&self, activity: A, activities: I) -> bool
     where
-        A: BnStrCompatible,
+        A: AsCStr,
         I: IntoIterator,
-        I::Item: BnStrCompatible,
+        I::Item: AsCStr,
     {
-        let input_list: Vec<_> = activities
-            .into_iter()
-            .map(|a| a.into_bytes_with_nul())
-            .collect();
-        let mut input_list_ptr: Vec<*const _> = input_list
-            .iter()
-            .map(|x| x.as_ref().as_ptr() as *const c_char)
-            .collect();
+        let activities = activities.into_iter().collect::<Vec<_>>();
+        let input_list: Vec<_> = activities.iter().map(|a| a.as_cstr()).collect();
+        let mut input_list_ptr: Vec<_> = input_list.iter().map(|x| x.as_ptr()).collect();
         unsafe {
             BNWorkflowInsertAfter(
                 self.handle.as_ptr(),
-                activity.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
+                activity.as_cstr().as_ptr(),
                 input_list_ptr.as_mut_ptr(),
                 input_list.len(),
             )
@@ -558,29 +502,20 @@ impl Workflow {
     }
 
     /// Remove the specified `activity`
-    pub fn remove<A: BnStrCompatible>(&self, activity: A) -> bool {
-        unsafe {
-            BNWorkflowRemove(
-                self.handle.as_ptr(),
-                activity.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
-            )
-        }
+    pub fn remove<A: AsCStr>(&self, activity: A) -> bool {
+        unsafe { BNWorkflowRemove(self.handle.as_ptr(), activity.as_cstr().as_ptr()) }
     }
 
     /// Replace the specified `activity`.
     ///
     /// * `activity` - the Activity to replace
     /// * `new_activity` - the replacement Activity
-    pub fn replace<A: BnStrCompatible, N: BnStrCompatible>(
-        &self,
-        activity: A,
-        new_activity: N,
-    ) -> bool {
+    pub fn replace<A: AsCStr, N: AsCStr>(&self, activity: A, new_activity: N) -> bool {
         unsafe {
             BNWorkflowReplace(
                 self.handle.as_ptr(),
-                activity.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
-                new_activity.into_bytes_with_nul().as_ref().as_ptr() as *const c_char,
+                activity.as_cstr().as_ptr(),
+                new_activity.as_cstr().as_ptr(),
             )
         }
     }
@@ -589,17 +524,16 @@ impl Workflow {
     ///
     /// * `activity` - if specified, generate the Flowgraph using `activity` as the root
     /// * `sequential` - whether to generate a **Composite** or **Sequential** style graph
-    pub fn graph<A: BnStrCompatible>(
+    pub fn graph<A: AsCStr>(
         &self,
         activity: A,
         sequential: Option<bool>,
     ) -> Option<Ref<FlowGraph>> {
         let sequential = sequential.unwrap_or(false);
-        let activity_name = activity.into_bytes_with_nul();
         let graph = unsafe {
             BNWorkflowGetGraph(
                 self.handle.as_ptr(),
-                activity_name.as_ref().as_ptr() as *const c_char,
+                activity.as_cstr().as_ptr(),
                 sequential,
             )
         };
diff --git a/rust/tests/websocket.rs b/rust/tests/websocket.rs
index 97a4ae2ba..01d2d7912 100644
--- a/rust/tests/websocket.rs
+++ b/rust/tests/websocket.rs
@@ -1,6 +1,6 @@
 use binaryninja::headless::Session;
 use binaryninja::rc::Ref;
-use binaryninja::string::BnStrCompatible;
+use binaryninja::string::AsCStr;
 use binaryninja::websocket::{
     register_websocket_provider, CoreWebsocketClient, CoreWebsocketProvider, WebsocketClient,
     WebsocketClientCallback, WebsocketProvider,
@@ -34,8 +34,8 @@ impl WebsocketClient for MyWebsocketClient {
     fn connect<I, K, V>(&self, host: &str, _headers: I) -> bool
     where
         I: IntoIterator<Item = (K, V)>,
-        K: BnStrCompatible,
-        V: BnStrCompatible,
+        K: AsCStr,
+        V: AsCStr,
     {
         assert_eq!(host, "url");
         true