Rust functions that return allocated strings
Returning an allocated string via FFI is complicated for the same reason that returning an object is: the Rust allocator can be different from the allocator on the other side of the FFI boundary. It also has the same restrictions dealing with NUL-terminated strings as passing a string argument.
extern crate libc;
use libc::c_char;
use std::ffi::CString;
use std::iter;
#[no_mangle]
pub extern "C" fn theme_song_generate(length: u8) -> *mut c_char {
let mut song = String::from("š£ ");
song.extend(iter::repeat("na ").take(length as usize));
song.push_str("Batman! š£");
let c_str_song = CString::new(song).unwrap();
c_str_song.into_raw()
}
#[no_mangle]
pub extern "C" fn theme_song_free(s: *mut c_char) {
unsafe {
if s.is_null() {
return;
}
CString::from_raw(s)
};
}
Here we use a pair of methods into_raw
and
from_raw
. These convert a CString
into a raw pointer
that may be passed across the FFI boundary. Ownership of the string is
transferred to the caller, but the caller must return the string to
Rust in order to properly deallocate the memory.
C
#include <stdio.h>
#include <stdint.h>
extern char *
theme_song_generate(uint8_t length);
extern void
theme_song_free(char *);
int main(void) {
char *song = theme_song_generate(5);
printf("%s\n", song);
theme_song_free(song);
}
Thereās not much interesting for the C version: the char *
is
returned, can be printed, and then is transferred back to be freed.
Ruby
require 'ffi'
class ThemeSong < FFI::AutoPointer
def self.release(ptr)
Binding.free(ptr)
end
def to_s
@str ||= self.read_string.force_encoding('UTF-8')
end
module Binding
extend FFI::Library
ffi_lib 'string_return'
attach_function :generate, :theme_song_generate,
[:uint8], ThemeSong
attach_function :free, :theme_song_free,
[ThemeSong], :void
end
end
puts ThemeSong::Binding.generate(5)
Because the string is allocated, we need to make sure to deallocate it
when it goes out of scope. Like an object, we subclass
FFI::AutoPointer
to automatically free the pointer for us.
We define to_s
to lazily convert the raw string to a Ruby string
using the UTF-8 encoding and memoize the result. Any string generated
by Rust will be valid UTF-8.
Python
#!/usr/bin/env python3
import sys, ctypes
from ctypes import c_void_p, c_uint8
prefix = {'win32': ''}.get(sys.platform, 'lib')
extension = {'darwin': '.dylib', 'win32': '.dll'}.get(sys.platform, '.so')
lib = ctypes.cdll.LoadLibrary(prefix + "string_return" + extension)
lib.theme_song_generate.argtypes = (c_uint8, )
lib.theme_song_generate.restype = c_void_p
lib.theme_song_free.argtypes = (c_void_p, )
def themeSongGenerate(count):
ptr = lib.theme_song_generate(count)
try:
return ctypes.cast(ptr, ctypes.c_char_p).value.decode('utf-8')
finally:
lib.theme_song_free(ptr)
print(themeSongGenerate(5))
We must use c_void_p
instead of c_char_p
as a return value of type
c_char_p
will be automatically converted to a Python string. This
string would be improperly freed by Python, instead of by Rust.
We cast the c_void_p
to a c_char_p
, grab the value, and encode the
raw bytes as a UTF-8 string.
Haskell
{-# LANGUAGE ForeignFunctionInterface #-}
import Data.Word (Word8)
import Foreign.Ptr (nullPtr)
import Foreign.C.String (CString(..), peekCString)
foreign import ccall unsafe "theme_song_generate"
theme_song_generate :: Word8 -> IO (CString)
foreign import ccall unsafe "theme_song_free"
theme_song_free :: CString -> IO ()
createThemeSong :: Word8 -> IO (Maybe (String))
createThemeSong len = do
ptr <- theme_song_generate len
if ptr /= nullPtr
then do
str <- peekCString ptr
theme_song_free ptr
return $ Just str
else
return Nothing
main :: IO ()
main = do
song <- createThemeSong 5
case song of
Nothing -> putStrLn "Unable to create theme song"
Just str -> putStrLn str
After calling the FFI method, we check to see if the string is
NULL
. If not, we convert it into a Haskell string using
peekCString
and free the Rust string.
Node.js
const ffi = require('ffi-napi');
const lib = ffi.Library('libstring_return', {
theme_song_generate: ['char *', ['uint8']],
theme_song_free: ['void', ['char *']],
});
function themeSongGenerate(len) {
const songPtr = lib.theme_song_generate(len);
try {
return songPtr.readCString();
} finally {
lib.theme_song_free(songPtr);
}
}
console.log(themeSongGenerate(5));
The string is returned as a char *
, which we can convert to a
JavaScript string by calling readCString
before passing it back to
be freed.
C#
using System;
using System.Runtime.InteropServices;
using System.Text;
internal class Native
{
[DllImport("string_return")]
internal static extern ThemeSongHandle theme_song_generate(byte length);
[DllImport("string_return")]
internal static extern void theme_song_free(IntPtr song);
}
internal class ThemeSongHandle : SafeHandle
{
public ThemeSongHandle() : base(IntPtr.Zero, true) {}
public override bool IsInvalid
{
get { return this.handle == IntPtr.Zero; }
}
public string AsString()
{
int len = 0;
while (Marshal.ReadByte(handle, len) != 0) { ++len; }
byte[] buffer = new byte[len];
Marshal.Copy(handle, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
}
protected override bool ReleaseHandle()
{
if (!this.IsInvalid)
{
Native.theme_song_free(handle);
}
return true;
}
}
public class ThemeSong : IDisposable
{
private ThemeSongHandle song;
private string songString;
public ThemeSong(byte length)
{
song = Native.theme_song_generate(length);
}
public override string ToString()
{
if (songString == null) {
songString = song.AsString();
}
return songString;
}
public void Dispose()
{
song.Dispose();
}
static public void Main()
{
var song = new ThemeSong(5);
Console.WriteLine("{0}", song);
}
}
We follow a similar pattern to the object example: the Rust string is
contained within a subclass of SafeHandle
and a wrapper class
ThemeSong
ensures that the handle is disposed properly.
Unfortunately, there is no easy way to read the pointer as a UTF-8 string. C# has cases for ANSI strings and for āUnicodeā strings (really UCS-2), but nothing for UTF-8. We need to write that ourselves.
Julia
#!/usr/bin/env julia
using Libdl
libname = "string_return"
if !Sys.iswindows()
libname = "lib$(libname)"
end
lib = Libdl.dlopen(libname)
themesonggenerator_sym = Libdl.dlsym(lib, :theme_song_generate)
themesongfree_sym = Libdl.dlsym(lib, :theme_song_free)
function generatethemesong(n::Int)
s = ccall(
themesonggenerator_sym,
Cstring, (UInt8,),
n)
out = unsafe_string(s)
ccall(
themesongfree_sym,
Cvoid, (Cstring,),
s
)
out
end
song = generatethemesong(5)
println(song)
We use the Cstring
data type to represent a NUL-terminated string.
Rather than holding the allocated string in Julia space, this example
constructs a copy of the string with unsafe_string
, to be managed
by Julia, and transfers the Rust string back afterwards. The
objects section provides an example where the
resource is kept alive in Julia.