interoptopus 0.16.0

The polyglot bindings generator for your library (C#, C, Python, ...). 🐙
Documentation
//! Null-terminated C string pointer that becomes `string` in supported backends.
//!
//! [`CStrPtr`] is a thin `*const c_char` wrapper for passing read-only,
//! null-terminated, ASCII-compatible strings across the FFI boundary.
//! For owned UTF-8 strings, prefer [`ffi::String`](crate::pattern::string::String).
//!
//! # Example
//!
//! ```
//! use interoptopus::ffi;
//!
//! #[ffi]
//! pub fn call_with_string(s: ffi::CStrPtr) {
//!     let _rust_str = s.as_str().unwrap_or("");
//! }
//! ```
//!
//! In C# and similar backends, the parameter is exposed as a plain `string`.
//! In C, it maps to `uint8_t*`.
//!
use crate::inventory::{Inventory, TypeId};
use crate::lang::meta::{Docs, Emission, FileEmission, Visibility};
use crate::lang::types::{Type, TypeInfo, TypeKind, TypePattern, WireIO};
use crate::wire::SerializationError;
use crate::{Error, bad_wire};
use std::ffi::CStr;
use std::io::{Read, Write};
use std::marker::PhantomData;
use std::option::Option::None;
use std::os::raw::c_char;
use std::ptr::null;

static EMPTY: &[u8] = b"\0";

/// Represents a `*const char` on FFI level pointing to an `0x0` terminated ASCII string.
///
/// # Antipattern
///
/// It's discouraged to use [`FFIOption`](crate::pattern::option::Option) with [`CStrPtr`]
/// and some backend might not generate proper bindings (like C#).
///
/// Instead use [`CStrPtr`] alone since it already has a pointer that's nullable.
/// In this case, [`CStrPtr::as_c_str()`] will return [`None`] and [`CStrPtr::as_str`]
/// will return an [`Error`].
#[repr(transparent)]
#[derive(Debug)]
pub struct CStrPtr<'a> {
    ptr: *const c_char,
    _phantom: PhantomData<&'a ()>,
}

// Safety: `CStrPointer` is a transparent wrapper around a pointer. From Rust
//         we only allow safe construction, from interop it's up to the FFI caller.
unsafe impl Send for CStrPtr<'_> {}
unsafe impl Sync for CStrPtr<'_> {}

impl Default for CStrPtr<'_> {
    fn default() -> Self {
        Self { ptr: null(), _phantom: PhantomData }
    }
}

impl<'a> CStrPtr<'a> {
    #[must_use]
    pub fn empty() -> Self {
        Self { ptr: EMPTY.as_ptr().cast(), _phantom: PhantomData }
    }

    /// Create a `CStrPointer` from a `&[u8]` slice reference.
    ///
    /// The parameter `cstr_with_nul` must contain nul (`0x0`), but it does not need to contain nul
    /// at the end.
    ///
    /// # Errors
    /// Can fail if the string contains a nul.
    pub fn from_slice_with_nul(cstr_with_nul: &'a [u8]) -> Result<Self, Error> {
        // Check we actually contain one `0x0`.
        if !cstr_with_nul.contains(&0) {
            return Err(Error::nul_terminated());
        }

        // Can't do this, C# treats ASCII as extended and bytes > 127 might show up, which
        // is going to be a problem when returning a string we previously accepted.
        //
        // Any previous characters must not be extended ASCII.
        // if ascii_with_nul.iter().any(|x| *x > 127) {
        //     return Err(Error::Ascii);
        // }

        Ok(Self { ptr: cstr_with_nul.as_ptr().cast(), _phantom: PhantomData })
    }

    /// Create a pointer from a `CStr`.
    #[must_use]
    pub fn from_cstr(cstr: &'a CStr) -> Self {
        Self { ptr: cstr.as_ptr(), _phantom: PhantomData }
    }

    /// Create a [`CStr`] for the pointer.
    #[must_use]
    pub fn as_c_str(&self) -> Option<&'a CStr> {
        if self.ptr.is_null() {
            None
        } else {
            // TODO: Write something about safety
            unsafe { Some(CStr::from_ptr(self.ptr)) }
        }
    }

    /// Attempts to return a Rust `str`.
    ///
    /// # Errors
    /// Can fail if the string was null.
    pub fn as_str(&self) -> Result<&'a str, Error> {
        Ok(self.as_c_str().ok_or_else(Error::null)?.to_str()?)
    }
}

unsafe impl TypeInfo for CStrPtr<'_> {
    const WIRE_SAFE: bool = false;
    const RAW_SAFE: bool = true;
    const ASYNC_SAFE: bool = false;
    const SERVICE_SAFE: bool = false;
    const SERVICE_CTOR_SAFE: bool = false;

    fn id() -> TypeId {
        TypeId::new(0xDE450364E9ADDBA5DC9A6C5BBEC7759F)
    }

    fn kind() -> TypeKind {
        TypeKind::TypePattern(TypePattern::CStrPointer)
    }

    fn ty() -> Type {
        Type {
            emission: Emission::FileEmission(FileEmission::Common),
            docs: Docs::empty(),
            visibility: Visibility::Public,
            name: "CStrPtr".to_string(),
            kind: Self::kind(),
        }
    }

    fn register(inventory: &mut impl Inventory) {
        inventory.register_type(Self::id(), Self::ty());
    }
}

unsafe impl WireIO for CStrPtr<'_> {
    fn write(&self, _: &mut impl Write) -> Result<(), SerializationError> {
        bad_wire!()
    }

    fn read(_: &mut impl Read) -> Result<Self, SerializationError> {
        bad_wire!()
    }

    fn live_size(&self) -> usize {
        bad_wire!()
    }
}