interoptopus 0.16.0-alpha.14

The polyglot bindings generator for your library (C#, C, Python, ...). 🐙
Documentation
//! Owned, FFI-safe UTF-8 string.
//!
//! [`String`] is a `repr(C)` type that mirrors the layout of a Rust
//! `std::string::String` (pointer + length + capacity). It owns its
//! allocation and can be passed by value across the FFI boundary.
//! Backends generate idiomatic string conversions — for example, in C#
//! it becomes a managed `string` with automatic marshalling.
//!
//! The [`builtins_string!`](crate::builtins_string) macro must be
//! registered in the inventory so that backends can emit the required
//! helper functions (create / destroy / clone).
//!
//! # Example
//!
//! ```
//! use interoptopus::ffi;
//!
//! #[ffi]
//! pub fn greeting(name: ffi::String) -> ffi::String {
//!     let msg = format!("hello, {}", name.as_str());
//!     ffi::String::from_string(msg)
//! }
//! ```

use crate::inventory::{Inventory, TypeId};
use crate::lang::meta::{Emission, FileEmission, Visibility};
use crate::lang::types::{Type, TypeInfo, TypeKind, TypePattern, WireIO};
use crate::wire::SerializationError;
use std::io::{Read, Write};
use std::mem::forget;

/// FFI analog of [`std::string::String`].
///
/// See the [module documentation](crate::pattern::string) for more details and examples.
#[derive(Debug)]
#[repr(C)]
pub struct String {
    ptr: *mut u8,
    len: u64,
    capacity: u64,
}

unsafe impl Send for String {}
unsafe impl Sync for String {}

impl String {
    #[must_use]
    pub fn from_string(mut s: std::string::String) -> Self {
        let ptr = s.as_mut_ptr();
        let capacity = s.capacity() as u64;
        let len = s.len() as u64;
        forget(s);
        Self { ptr, len, capacity }
    }

    #[must_use]
    #[allow(clippy::cast_possible_truncation)]
    pub fn as_str(&self) -> &str {
        if self.ptr.is_null() {
            return "";
        }

        unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.ptr, self.len as usize)) }
    }

    #[must_use]
    #[allow(clippy::cast_possible_truncation)]
    pub fn into_string(self) -> std::string::String {
        let rval = unsafe { std::string::String::from_raw_parts(self.ptr, self.len as usize, self.capacity as usize) };
        forget(self);
        rval
    }
}

impl From<std::string::String> for String {
    fn from(value: std::string::String) -> Self {
        Self::from_string(value)
    }
}

impl From<String> for std::string::String {
    fn from(value: String) -> Self {
        value.into_string()
    }
}

impl Clone for String {
    fn clone(&self) -> Self {
        Self::from_string(self.as_str().to_string())
    }
}

impl Drop for String {
    #[allow(clippy::cast_possible_truncation)]
    fn drop(&mut self) {
        if self.ptr.is_null() {
            return;
        }
        unsafe {
            let _ = std::string::String::from_raw_parts(self.ptr, self.len as usize, self.capacity as usize);
        }
    }
}

unsafe impl TypeInfo for String {
    const WIRE_SAFE: bool = true;
    const RAW_SAFE: bool = true;
    const ASYNC_SAFE: bool = true;
    const SERVICE_SAFE: bool = false;
    const SERVICE_CTOR_SAFE: bool = false;

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

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

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

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

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

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

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

/// Emits and registers helpers for [`ffi::String`](crate::pattern::string::String).
///
/// Backends (e.g., C#) use these functions internally so that foreign code can
/// manage Rust-owned strings without manual pointer arithmetic.
///
/// # Usage
///
/// Call once in your inventory function and register the result:
///
/// ```rust
/// # use interoptopus::inventory::RustInventory;
/// # use interoptopus::builtins_string;
/// pub fn inventory() -> RustInventory {
///     RustInventory::new()
///         .register(builtins_string!())
///         // ... other registrations ...
///         .validate()
/// }
/// ```
///
/// # Implementation Details
///
/// This macro also generates the following FFI functions:
/// - `interoptopus_string_create` — creates an `ffi::String` from a raw UTF-8 pointer and length.
/// - `interoptopus_string_destroy` — drops an `ffi::String`, freeing its memory.
/// - `interoptopus_string_clone` — clones an `ffi::String`.
#[macro_export]
macro_rules! builtins_string {
    () => {{
        #[$crate::ffi]
        pub fn interoptopus_string_create(utf8: *const ::std::ffi::c_void, len: u64, rval: &mut ::std::mem::MaybeUninit<$crate::pattern::string::String>) -> i64 {
            let slice = if utf8.is_null() {
                &[]
            } else {
                unsafe { ::std::slice::from_raw_parts::<u8>(utf8.cast(), len as usize) }
            };
            let vec = slice.to_vec();
            let string = unsafe { ::std::string::String::from_utf8_unchecked(vec) };
            rval.write($crate::pattern::string::String::from_string(string));
            0
        }

        #[$crate::ffi]
        pub fn interoptopus_string_destroy(utf8: $crate::pattern::string::String) -> i64 {
            0
        }

        #[$crate::ffi]
        pub fn interoptopus_string_clone(utf8: &$crate::pattern::string::String, rval: &mut ::std::mem::MaybeUninit<$crate::pattern::string::String>) -> i64 {
            rval.write(utf8.clone());
            0
        }

        |x: &mut $crate::inventory::RustInventory| {
            <interoptopus_string_create as $crate::lang::function::FunctionInfo>::register(x);
            <interoptopus_string_destroy as $crate::lang::function::FunctionInfo>::register(x);
            <interoptopus_string_clone as $crate::lang::function::FunctionInfo>::register(x);
        }
    }};
}