feat!(src/macros): Store allocation location in all strings
This makes it possible to automatically free the string in the correct way expected by both c and rust. Rust strings can now just be freed by the rust allocator, whilst c strings are forgotten, so that the c allocator may free them. BREAKING CHANGE: All usage of explicit mentions `std::string::String` in your `handle_cmd` function will need to be replaced by `trixy::types::newtypes::String`. Otherwise these types should provide the same methods.
This commit is contained in:
parent
86427b0c1a
commit
0a60c894d2
|
@ -20,9 +20,10 @@
|
|||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{env, ffi::c_int, mem};
|
||||
use std::{env, ffi::c_int};
|
||||
|
||||
use libloading::{Library, Symbol};
|
||||
use trixy::types::newtypes;
|
||||
|
||||
use crate::dogs::{DogType, TrainedDog};
|
||||
|
||||
|
@ -30,6 +31,7 @@ include!(concat!(env!("OUT_DIR"), "/api.rs"));
|
|||
|
||||
// run `cargo run --bin api > ./src/bin/main/generated_api.rs` to output the generated api
|
||||
// mod generated_api;
|
||||
// pub use generated_api::*;
|
||||
|
||||
fn handle_cmd(cmd: Commands) {
|
||||
match cmd {
|
||||
|
@ -38,9 +40,11 @@ fn handle_cmd(cmd: Commands) {
|
|||
let output = format!("Hi {}!", name);
|
||||
println!("(rust): {}", output);
|
||||
trixy_output.send(output.into()).expect("Will work");
|
||||
mem::forget(name);
|
||||
}
|
||||
dogs::Dogs::train_dog { trixy_output, dog } => {
|
||||
dogs::Dogs::train_dog {
|
||||
trixy_output,
|
||||
dog: _,
|
||||
} => {
|
||||
trixy_output
|
||||
.send(
|
||||
TrainedDog {
|
||||
|
@ -51,23 +55,19 @@ fn handle_cmd(cmd: Commands) {
|
|||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
mem::forget(dog);
|
||||
}
|
||||
dogs::Dogs::guess_my_favourite_dog {
|
||||
trixy_output,
|
||||
callback,
|
||||
} => {
|
||||
let fav_dog = callback("Frank".into(), 30);
|
||||
let dog_name: String = fav_dog.name.clone().try_into().unwrap();
|
||||
let dog_name: newtypes::String = fav_dog.name.clone().try_into().unwrap();
|
||||
println!("(rust): They want a dog named: {}", dog_name);
|
||||
trixy_output.send(DogType::Cat.into()).unwrap();
|
||||
|
||||
mem::forget(dog_name);
|
||||
}
|
||||
},
|
||||
Commands::outstanding { name } => {
|
||||
println!("(rust): {} is outstanding!", name);
|
||||
mem::forget(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,9 +28,6 @@ use crate::parser::command_spec::{Identifier, NamedType, Type};
|
|||
mod doc_named_type;
|
||||
mod named_type;
|
||||
|
||||
// pub use doc_named_type::*;
|
||||
// pub use named_type::*;
|
||||
|
||||
impl Type {
|
||||
pub fn to_c(&self) -> TokenStream2 {
|
||||
match self {
|
||||
|
@ -82,7 +79,7 @@ impl Type {
|
|||
|
||||
let type_name = trixy_build_in_types
|
||||
.first()
|
||||
.expect("The names should not be dublicated, this should be the only value");
|
||||
.expect("The names should not be duplicated, this should be the only value");
|
||||
|
||||
match *type_name {
|
||||
"Result" => {
|
||||
|
|
|
@ -26,7 +26,6 @@ use quote::quote;
|
|||
use crate::parser::command_spec::{Function, Identifier, NamedType, Type};
|
||||
|
||||
impl Function {
|
||||
// was called function_identifier_to_rust
|
||||
pub fn to_rust_identifier<F>(
|
||||
&self,
|
||||
input_fmt_fn: fn(&NamedType) -> TokenStream2,
|
||||
|
|
|
@ -61,16 +61,22 @@ impl Type {
|
|||
}
|
||||
}
|
||||
pub fn to_rust_typical(identifier: &Identifier, generic_args: &Vec<Type>) -> TokenStream2 {
|
||||
match identifier {
|
||||
Identifier { name, variant: _ } if name == "String" => {
|
||||
// We wrap over the string to ensure that we know where it was allocated.
|
||||
quote! {
|
||||
trixy::types::newtypes::String
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let ident = identifier.to_rust();
|
||||
let namespaces_path = Variant::to_rust_path(&identifier.variant);
|
||||
|
||||
let nasp_path = if let Some(nasp_path) = Variant::to_rust_path(&identifier.variant) {
|
||||
if nasp_path.is_empty() {
|
||||
let nasp_path = if let Some(path) = &Variant::to_rust_path(&identifier.variant) {
|
||||
if path.is_empty() {
|
||||
quote! {
|
||||
crate ::
|
||||
}
|
||||
} else {
|
||||
let path = namespaces_path;
|
||||
quote! {
|
||||
#path ::
|
||||
}
|
||||
|
@ -84,10 +90,13 @@ impl Type {
|
|||
#nasp_path #ident
|
||||
}
|
||||
} else {
|
||||
let generics: Vec<TokenStream2> = generic_args.iter().map(Type::to_rust).collect();
|
||||
let generics: Vec<TokenStream2> =
|
||||
generic_args.iter().map(Type::to_rust).collect();
|
||||
quote! {
|
||||
#nasp_path #ident <#(#generics),*>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
pub mod error;
|
||||
pub mod traits;
|
||||
pub mod types_list;
|
||||
pub mod newtypes;
|
||||
|
||||
pub use types_list::*;
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
//! 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,32 +20,51 @@
|
|||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use crate::types::String;
|
||||
use crate::types::{
|
||||
newtypes::{self, AllocLocation},
|
||||
types_list,
|
||||
};
|
||||
use std::ffi::CString;
|
||||
|
||||
use super::convert_trait::Convertible;
|
||||
|
||||
impl From<std::string::String> for String {
|
||||
impl From<std::string::String> for types_list::String {
|
||||
fn from(value: std::string::String) -> Self {
|
||||
let cstring =
|
||||
CString::new(value).expect("The input is a valid String, this always succeeds");
|
||||
let c_char = cstring.into_ptr();
|
||||
String(c_char)
|
||||
types_list::String(c_char)
|
||||
}
|
||||
}
|
||||
/// Plainly here for convenience
|
||||
impl From<&str> for String {
|
||||
impl From<&str> for types_list::String {
|
||||
fn from(value: &str) -> Self {
|
||||
value.to_owned().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for std::string::String {
|
||||
impl From<newtypes::String> for types_list::String {
|
||||
fn from(value: newtypes::String) -> Self {
|
||||
// NOTE: This is not really performant, but necessary as we otherwise would need c to
|
||||
// differentiate between c allocated strings (which are freed by `free`) and rust allocated strings
|
||||
// (which are freed by `string_free`) <2024-05-04>
|
||||
let value_string = value.into_std_string();
|
||||
value_string.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<types_list::String> for newtypes::String {
|
||||
type Error = crate::types::error::TypeConversionError;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: types_list::String) -> Result<Self, Self::Error> {
|
||||
let cstring = CString::from_ptr(value.0)?;
|
||||
let string = cstring.into_string()?;
|
||||
Ok(string)
|
||||
|
||||
Ok(newtypes::String {
|
||||
data: Some(string),
|
||||
// TODO: You could of course do a rust-only round trip, but I think it's reasonable to
|
||||
// assume, that all types_list::String values come from C <2024-05-04>
|
||||
alloc_location: AllocLocation::C,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Reference in New Issue