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/>.
|
* 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 libloading::{Library, Symbol};
|
||||||
|
use trixy::types::newtypes;
|
||||||
|
|
||||||
use crate::dogs::{DogType, TrainedDog};
|
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
|
// run `cargo run --bin api > ./src/bin/main/generated_api.rs` to output the generated api
|
||||||
// mod generated_api;
|
// mod generated_api;
|
||||||
|
// pub use generated_api::*;
|
||||||
|
|
||||||
fn handle_cmd(cmd: Commands) {
|
fn handle_cmd(cmd: Commands) {
|
||||||
match cmd {
|
match cmd {
|
||||||
|
@ -38,9 +40,11 @@ fn handle_cmd(cmd: Commands) {
|
||||||
let output = format!("Hi {}!", name);
|
let output = format!("Hi {}!", name);
|
||||||
println!("(rust): {}", output);
|
println!("(rust): {}", output);
|
||||||
trixy_output.send(output.into()).expect("Will work");
|
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
|
trixy_output
|
||||||
.send(
|
.send(
|
||||||
TrainedDog {
|
TrainedDog {
|
||||||
|
@ -51,23 +55,19 @@ fn handle_cmd(cmd: Commands) {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
mem::forget(dog);
|
|
||||||
}
|
}
|
||||||
dogs::Dogs::guess_my_favourite_dog {
|
dogs::Dogs::guess_my_favourite_dog {
|
||||||
trixy_output,
|
trixy_output,
|
||||||
callback,
|
callback,
|
||||||
} => {
|
} => {
|
||||||
let fav_dog = callback("Frank".into(), 30);
|
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);
|
println!("(rust): They want a dog named: {}", dog_name);
|
||||||
trixy_output.send(DogType::Cat.into()).unwrap();
|
trixy_output.send(DogType::Cat.into()).unwrap();
|
||||||
|
|
||||||
mem::forget(dog_name);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Commands::outstanding { name } => {
|
Commands::outstanding { name } => {
|
||||||
println!("(rust): {} is 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 doc_named_type;
|
||||||
mod named_type;
|
mod named_type;
|
||||||
|
|
||||||
// pub use doc_named_type::*;
|
|
||||||
// pub use named_type::*;
|
|
||||||
|
|
||||||
impl Type {
|
impl Type {
|
||||||
pub fn to_c(&self) -> TokenStream2 {
|
pub fn to_c(&self) -> TokenStream2 {
|
||||||
match self {
|
match self {
|
||||||
|
@ -82,7 +79,7 @@ impl Type {
|
||||||
|
|
||||||
let type_name = trixy_build_in_types
|
let type_name = trixy_build_in_types
|
||||||
.first()
|
.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 {
|
match *type_name {
|
||||||
"Result" => {
|
"Result" => {
|
||||||
|
|
|
@ -26,7 +26,6 @@ use quote::quote;
|
||||||
use crate::parser::command_spec::{Function, Identifier, NamedType, Type};
|
use crate::parser::command_spec::{Function, Identifier, NamedType, Type};
|
||||||
|
|
||||||
impl Function {
|
impl Function {
|
||||||
// was called function_identifier_to_rust
|
|
||||||
pub fn to_rust_identifier<F>(
|
pub fn to_rust_identifier<F>(
|
||||||
&self,
|
&self,
|
||||||
input_fmt_fn: fn(&NamedType) -> TokenStream2,
|
input_fmt_fn: fn(&NamedType) -> TokenStream2,
|
||||||
|
|
|
@ -61,32 +61,41 @@ impl Type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn to_rust_typical(identifier: &Identifier, generic_args: &Vec<Type>) -> TokenStream2 {
|
pub fn to_rust_typical(identifier: &Identifier, generic_args: &Vec<Type>) -> TokenStream2 {
|
||||||
let ident = identifier.to_rust();
|
match identifier {
|
||||||
let namespaces_path = Variant::to_rust_path(&identifier.variant);
|
Identifier { name, variant: _ } if name == "String" => {
|
||||||
|
// We wrap over the string to ensure that we know where it was allocated.
|
||||||
let nasp_path = if let Some(nasp_path) = Variant::to_rust_path(&identifier.variant) {
|
|
||||||
if nasp_path.is_empty() {
|
|
||||||
quote! {
|
quote! {
|
||||||
crate ::
|
trixy::types::newtypes::String
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let path = namespaces_path;
|
|
||||||
quote! {
|
|
||||||
#path ::
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
_ => {
|
||||||
quote! {}
|
let ident = identifier.to_rust();
|
||||||
};
|
|
||||||
|
|
||||||
if generic_args.is_empty() {
|
let nasp_path = if let Some(path) = &Variant::to_rust_path(&identifier.variant) {
|
||||||
quote! {
|
if path.is_empty() {
|
||||||
#nasp_path #ident
|
quote! {
|
||||||
}
|
crate ::
|
||||||
} else {
|
}
|
||||||
let generics: Vec<TokenStream2> = generic_args.iter().map(Type::to_rust).collect();
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
#nasp_path #ident <#(#generics),*>
|
#path ::
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if generic_args.is_empty() {
|
||||||
|
quote! {
|
||||||
|
#nasp_path #ident
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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 error;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
pub mod types_list;
|
pub mod types_list;
|
||||||
|
pub mod newtypes;
|
||||||
|
|
||||||
pub use types_list::*;
|
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/>.
|
* 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 std::ffi::CString;
|
||||||
|
|
||||||
use super::convert_trait::Convertible;
|
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 {
|
fn from(value: std::string::String) -> Self {
|
||||||
let cstring =
|
let cstring =
|
||||||
CString::new(value).expect("The input is a valid String, this always succeeds");
|
CString::new(value).expect("The input is a valid String, this always succeeds");
|
||||||
let c_char = cstring.into_ptr();
|
let c_char = cstring.into_ptr();
|
||||||
String(c_char)
|
types_list::String(c_char)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Plainly here for convenience
|
/// Plainly here for convenience
|
||||||
impl From<&str> for String {
|
impl From<&str> for types_list::String {
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: &str) -> Self {
|
||||||
value.to_owned().into()
|
value.to_owned().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<String> for std::string::String {
|
impl From<newtypes::String> for types_list::String {
|
||||||
type Error = crate::types::error::TypeConversionError;
|
fn from(value: newtypes::String) -> Self {
|
||||||
|
// NOTE: This is not really performant, but necessary as we otherwise would need c to
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
// differentiate between c allocated strings (which are freed by `free`) and rust allocated strings
|
||||||
let cstring = CString::from_ptr(value.0)?;
|
// (which are freed by `string_free`) <2024-05-04>
|
||||||
let string = cstring.into_string()?;
|
let value_string = value.into_std_string();
|
||||||
Ok(string)
|
value_string.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<types_list::String> for newtypes::String {
|
||||||
|
type Error = crate::types::error::TypeConversionError;
|
||||||
|
|
||||||
|
fn try_from(value: types_list::String) -> Result<Self, Self::Error> {
|
||||||
|
let cstring = CString::from_ptr(value.0)?;
|
||||||
|
let string = cstring.into_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