trixy 0.4.0

A rust crate used to generate multi-language apis for your application
Documentation
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* This file is part of the Trixy crate for Trinitrix.
*
* Trixy is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/

//! This module contains wrapper types for know rust types, where trixy needs to store additional
//! information

use std::{fmt::Display, mem, string};

#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct String {
    pub(crate) data: Option<string::String>,
    pub(crate) alloc_location: AllocLocation,
}

#[derive(Clone, Hash, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum AllocLocation {
    #[default]
    Rust,
    C,
}

impl From<&str> for String {
    fn from(value: &str) -> Self {
        Self {
            // It's cloned here:
            data: Some(value.to_owned()),
            // The string is always allocated in rust, as we clone it above this comment
            alloc_location: AllocLocation::Rust,
        }
    }
}

impl From<string::String> for String {
    fn from(value: string::String) -> Self {
        Self {
            data: Some(value),
            // We just assume that every std String is actually allocated in rust
            alloc_location: AllocLocation::Rust,
        }
    }
}

impl Display for String {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_str())
    }
}

impl String {
    pub fn as_str(&self) -> &str {
        &self
            .data
            .as_ref()
            .expect("The string should only be empty when it's dropped")
    }
    pub fn into_std_string(mut self) -> string::String {
        match self.alloc_location {
            AllocLocation::C => {
                // Re-allocate the string in rust
                let c_alloc_string = mem::take(&mut self.data).expect("It should only be Some");
                let rust_alloc_string = c_alloc_string.clone();

                // This is here because of the same reason as in the drop impl: we just can't free
                // a string allocated in c.
                mem::forget(c_alloc_string);

                rust_alloc_string
            }
            AllocLocation::Rust => {
                let string = mem::take(&mut self.data).expect("Will always be some");
                string
            }
        }
    }
}

impl Drop for String {
    fn drop(&mut self) {
        match self.alloc_location {
            AllocLocation::C => {
                let string = mem::take(&mut self.data);
                // C needs to free it's strings by itself. We cannot do the job of the c allocator,
                // thus just forget about it here
                mem::forget(string)
            }
            AllocLocation::Rust => {
                // A rust allocated string can be free normally. We don't need to do anything here.
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::types::types_list;

    use super::String;

    #[test]
    fn test_string_round_trip() {
        let start = "HI! I'm a nice string".to_owned();
        let wrapper: String = start.clone().into();

        assert_eq!(&start, wrapper.as_str());
        assert_eq!(start, wrapper.to_string());
        assert_eq!(start, wrapper.into_std_string());
    }

    #[test]
    fn test_string_round_trip_through_c() {
        let start = "HI! I'm a nice string".to_owned();

        let c_string: types_list::String = start.clone().into();
        let wrapper: String = Into::<types_list::String>::into(c_string)
            .try_into()
            .unwrap();

        assert_eq!(&start, wrapper.as_str());
        assert_eq!(start, wrapper.to_string());
        assert_eq!(start, wrapper.into_std_string());
    }
}