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;

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();

pub extern "C" fn theme_song_free(s: *mut c_char) {
    unsafe {
        if s.is_null() {

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.


#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);

There’s not much interesting for the C version: the char * is returned, can be printed, and then is transferred back to be freed.


require 'ffi'

class ThemeSong < FFI::AutoPointer
  def self.release(ptr)

  def to_s
    @str ||= self.read_string.force_encoding('UTF-8')

  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

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.


#!/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)
        return ctypes.cast(ptr, ctypes.c_char_p).value.decode('utf-8')


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.


{-# 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
      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.


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 {


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.


using System;
using System.Runtime.InteropServices;
using System.Text;

internal class Native
    internal static extern ThemeSongHandle theme_song_generate(byte length);
    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)

        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()

    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.


#!/usr/bin/env julia
using Libdl

libname = "string_return"
if !Sys.iswindows()
    libname = "lib$(libname)"

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(
        Cstring, (UInt8,),
    out = unsafe_string(s)
        Cvoid, (Cstring,),

song = generatethemesong(5)

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.