Calling c function with string

If I've the following c function:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char* concat(const char *str1, const char *str2)
{
    char *res;
    const char del[] = ", ";
    res = malloc(strlen(str1) + strlen(str2) + strlen(del) + 1);
    if (!res) {
        fprintf(stderr, "malloc() failed: insufficient memory!\n");
        return EXIT_FAILURE;
    }

    strcpy(res, str1);
    strcat(res, del);
    strcat(res, str2);

    printf("Result: '%s'\n", res);
    return res;
}

I can call it from c as below:

int main(void) {
    const char str1[] = "First";
    const char str2[] = "Second";
    char* s = concat(str1, str2);
    printf("Result: '%s'\n", s);
    free(s);
    return EXIT_SUCCESS;
}

I wanted to call the same function from Rust, but got confused:

  1. What function signature I've to use, I tried the below but thing it is wrong:
extern { fn concat(str1: &'static str, str2: &'static str) -> &'static str; }
  1. How to call it, is below correct:
fn main() {
    let str1: &str = "First";
    let str2: &str = "Second";
    let s: &str = unsafe { concat(str1, str2) };
    println!("Result: {}", s);
}
  1. What about freeing memory, what happen for the result will it remain in the memory, or free/dropped automatically upon exiting the unsafe block, or shall I do any thing for it?
  1. Get rid of all the 'statics. The return value simply can't be 'static (you're creating it at runtime, after all), and there's no reason to restrict either argument to only 'static strings.

  2. I think that's correct.

  3. The only "automatic" deallocating that happens in Rust is through implementations of the Drop trait. &str is a non-owning type, so it cannot and does not have a (non-trivial, deallocating) Drop implementation. Plus, memory must be freed by the same allocator that allocated it, so you need to go back into C to use C's free(). I believe what people typically do here is write a Rust wrapper type with a Drop impl which makes that call into C.

Note that the unsafe keyword has no effect on any of these semantics. All it does here is permit calling an unsafe/extern function. Variables declared inside a block get dropped at the end of a block, whether it's {} or unsafe {} or if {} or whatever kind of block. Your snippet only declares variables outside the unsafe {} block.

Thanks, I wrote the below:

extern { fn concat(str1: &str, str2: &str) -> &'static str; }

fn main() {
    let str1: &str = "First";
    let str2: &str = "Second";
    let s: &str = unsafe { concat(str1, str2) };
    println!("Result: {}", s);
}

And got the below:

You can't use &str across ffi boundries. You must explicitly pass around a raw pointers directly

std::os::raw::c_char;
extern "C" {
    fn concat(str1: *const c_char, str2: *const c_char) -> *mut c_char;
    fn c_free_string(str: *mut c_char); // this is added
}

Once you have this, you can wrap it in a safe api like so,

use std::ffi::CStr;
std::os::raw::c_char;

fn concat_cstr(str1: &CStr, str2: &CStr) -> Concat {
    let concated = unsafe { concat(str1.as_ptr(), str2.as_ptr()) };
    Concat(concated)
}

pub struct Concat(*mut c_char);

impl Drop for Concat {
    fn drop(&mut self) {
        unsafe { c_free_string(self.0) }
    }
}

impl Deref for Concat {
    type Target = CStr;
    
    fn deref(&self) -> &CStr {
        unsafe { CStr::from_ptr(self.0) }
    }
}
5 Likes

The std::ffi module docs go into detail about string representations.

2 Likes

Thank, but how to call it in my main function?

image

What is the meaning of this?

It accesses the first field of a tuple struct (like Concat). You can create a CStr like so CStr::from_bytes_with_nul(b"First\0").unwrap()

Almost there, but the Contact appear as address, tried pointering ad derefering but did not get it:
image

The code used (excluding freeing memory) is:

use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ops::Deref;

extern "C" {
    fn concat(str1: *const c_char, str2: *const c_char) -> *mut c_char;
}
#[derive(Debug)]
pub struct Concat(*mut c_char);

fn concat_cstr(str1: &CStr, str2: &CStr) -> Concat {
    let concated = unsafe { concat(str1.as_ptr(), str2.as_ptr()) };
    Concat(concated)
}

fn main() {
    let str1  = CString::new("First").unwrap();
    let str2 = CString::new("Second").unwrap();
    let s = concat_cstr(str1.as_c_str(), str2.as_c_str());
    println!("Result: {:?}", s);
}

You probably want to convert the result of concat() back into a &CStr that you can more easily use in Rust, rather than leaving it as a raw pointer. The CStr:: from_ptr method is intended for that, but you need to ensure the C code is passing a valid pointer (which it appears to in this case).

@RustyYato suggested a Deref implementation for the Concat type, which would wrap the call to from_ptr, and create a safe abstraction to manage the underlying pointer.

Don't derive Debug, instead you will have to implement it yourself. You can use Deref to convert &Concat to a &CStr, then print out that out.

use std::fmt;
impl fmt::Debug for Concat {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        CStr::fmt(self, f)
    }
}

println!("{:?}", concated_string)
3 Likes

Thanks a lot, not back for freeing memory issue, as I got error at:

impl Drop for Concat {
    fn drop(&mut self) {
        unsafe { c_free_string(self.0) }
    }
}

Which is:

error LNK2019: unresolved external symbol c_free_string referenced in function ZN62$LT$rust_webview..Concat$u20$as$u20$core..ops..
drop..Drop$GT$4drop17h012a6d16b98af5e1E

unresolved external symbol c_free_string

This is because you haven't written c_free_string function. C doesn't have automatic memory management, so every string that C returns is going to be leaked memory, unless you manually make a function to free that memory, and ensure that function is invoked.

If you can guarantee that concat used malloc to make that string, then in Rust you can use libc::free instead.

2 Likes

I got this error:

tried fixing it as &mut f then I got this error:

Tried fixing it by &mut f: fmt::Formatter and got this one:

image

Whoops, I forgot the signature of Debug::fmt, the second error shows exactly the problem. You need to change the signature to fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result

1 Like

Thanks a lot, deeply appreciate your support :slight_smile:

Now the working code became like this:
The c file:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void c_free_string(const char *str){
    free(str);
}

char *concat(const char *str1, const char *str2)
{
    char *res;
    const char del[] = ", ";
    res = malloc(strlen(str1) + strlen(str2) + strlen(del) + 1);
    if (!res) {
        fprintf(stderr, "malloc() failed: insufficient memory!\n");
        return EXIT_FAILURE;
    }

    strcpy(res, str1);
    strcat(res, del);
    strcat(res, str2);

    printf("Result: '%s'\n", res);
    return res;
}

The Rust file:

use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ops::Deref;
use std::fmt;

extern "C" {
    fn concat(str1: *const c_char, str2: *const c_char) -> *mut c_char;
    fn c_free_string(str: *mut c_char);
}

pub struct Concat(*mut c_char);
impl fmt::Debug for Concat {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        CStr::fmt(self, f)
    }
}
impl Drop for Concat {
    fn drop(&mut self) {
        unsafe { c_free_string(self.0) }
    }
}
impl Deref for Concat {
    type Target = CStr;

    fn deref(&self) -> &CStr {
        unsafe { CStr::from_ptr(self.0) }
    }
}

fn concat_cstr(str1: &CStr, str2: &CStr) -> Concat {
    let concated = unsafe { concat(str1.as_ptr(), str2.as_ptr()) };
    Concat(concated)
}

fn main() {
    let str1  = CString::new("First").unwrap();
    let str2 = CString::new("Second").unwrap();
    let concated_string = concat_cstr(str1.as_c_str(), str2.as_c_str());
    println!("{:?}", concated_string.to_str());
}
4 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.