Let's talk about safe and unsafe in Rust.
Google has published a document on Rust and C++ interoperability in the context of Chromium. It describes their criteria for the experience of calling C++ from Rust — minimal use of unsafe, no boilerplate beyond existing C++ declarations, and broad support for existing Chromium types.
The response to this document has included a lot of confusion and misinformation. Really, this is a continuation of a larger ongoing discussion of how to use unsafe properly. Rust FFI and unsafe are complicated and often subtle topics, so here I am going to attempt to summarize the issues and suggest how we might address them.
Recently I was passing ownership of a Rust array to a C function from a Box<[T]> and found that I actually didn’t know whether my code would lead to a memory leak. This lead me down a rabbit hole to understand how exactly slices work in Rust.
Foreign Function Interfaces (FFI) are a core mechanism for enabling integration of new languages into existing codebases or building on existing libraries. That said, the term “FFI” is often overloaded in ways that may be unclear or ambiguous, and the area can seem overwhelming to approach. In this post, I explain the two “directions” of FFI, some patterns for how FFI in each direction is handled in Rust and further break down some FFI design approaches.
When we started to build Ditto as a cross-platform SDK, we understood that it was untenable to create a specific port for each popular programming language. Instead, we opted to build the vast majority of shared code in Rust. Rust bought us a lot of features such as being easier to read, highly performant, and includes a modern build system and package manager. After the common code was built in Rust, we would then expose the primary APIs over a foreign function interface (FFI). For higher level programming languages like Swift, Java, and C#, we would create ergonomic bindings to Rust through calling the FFI C-headers.
While this FFI-strategy sounded straightforward on paper, in practice this didn't turn out so pretty. Our initial efforts primarily revolved around editing a singular C-FFI layer. We knew that C was the lingua franca of cross-language communication and initially assumed this singular layer to be the long term solution to writing FFI code. However, this FFI layer that we created began to grow unnaturally beyond it's initial purpose. As a rapidly iterating startup, non-trivial logic began to creep inside this FFI layer to compensate for the rising idiosyncracies and specific needs of each platform. As more logic began to creep into this FFI layer, we began to write more and more unit tests to guarantee stability of a layer that was only meant for passing functions and data around. As a side effect, we added more unit tests to the #[no_mangle] functions which frequently juggled several C-pointers.
Dart is a client-optimized language for fast apps on any platform, it make it easy to build the UI of your application and it is quite nice language to work with, it the language used by Flutter Framework, Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.
Rust is blazingly fast and memory-efficient, with no runtime or garbage collector, it can power performance-critical services, run on embedded devices, and easily integrate with other languages.
We are using both Rust and Dart (in Flutter) at Sunshine to enable open-source grant initiatives to easily operate in an on-chain ecosystem.
Almost all of our Code is written in Rust, that's why we needed to think about using the same code and the same logic in our client-side application, but How?
Every now and then when using native libraries from Rust you’ll be asked to pass a callback across the FFI boundary. The reasons are varied, but often this might be done to notify the caller when “interesting” things happen, for injecting logic (see the Strategy Pattern), or to handle the result of an asynchronous operation.
If this were normal Rust, we’d just accept a closure (e.g. a Box<dyn Fn(...)> or by being generic over any function-like type) and be done with it. However, when working with other languages you are reduced to the lowest common denominator, a the C language (or more specifically, the ABI and machine code in general) doesn’t understand generics or Rust’s “fat” pointers.
This means we need to be a little… creative.
Computers provide clock sources that allow a program to make assumptions about time passed. If all you need is measure the time passed between some instant a and instant b the Rust libstd provides you with Instant, a measurement of a monotonically nondecreasing clock (I'm sorry to inform you that guarantees about monotonically increasing clocks are lacking). Instants are an opaque thing and only good for getting you the difference between two of them, resulting in a Duration. Good enough if you can rely on the operating system to not lie to you.
The FFI-unwind project group, announced in this RFC, is working to extend the language to support unwinding that crosses FFI boundaries.
We have reached our first technical decision point, on a question we have been discussing internally for quite a while. This blog post lays out the arguments on each side of the issue and invites the Rust community to join us at an upcoming meeting to help finalize our decision, which will be formalized and published as our first language-change RFC. This RFC will propose an "MVP" specification for well-defined cross-language unwinding.
This is part 2 in our PHP FFI + Rust blog series. Previously we took a look at how the FFI feature can be enabled and used in PHP 7.4, and now we will jump over to Rust and see how we can create C-ABI libraries ourselves, which can then be loaded using PHP FFI.
Librsvg exports two public APIs: the C API that is in turn available to other languages through GObject Introspection, and the Rust API.
You could call this a use of the facade pattern on top of the rsvg_internals crate. That crate is the actual implementation of librsvg, and exports an interface with many knobs that are not exposed from the public APIs. The knobs are to allow for the variations in each of those APIs.
This post is about some interesting things that have come up during the creation/separation of those public APIs, and the implications of having an internals library that implements both.
I want to write a library in Rust that can be called from C and just as easily called from Rust code. The tooling makes it pretty easy, but I had to look in a few places to figure how it is supposed to work and get tests running in both languages.
This library provides a safe mechanism for calling C++ code from Rust and Rust code from C++, not subject to the many ways that things can go wrong when using bindgen or cbindgen to generate unsafe C-style bindings.
This doesn't change the fact that 100% of C++ code is unsafe. When auditing a project, you would be on the hook for auditing all the unsafe Rust code and all the C++ code. The core safety claim under this new model is that auditing just the C++ side would be sufficient to catch all problems, i.e. the Rust side can be 100% safe.
In this article, we will explore how to wrap those functions and make them safe for normal use. We’ll go over how to define a wrapper struct that handles initialization and cleanup, and describe some traits that describe how application developers can safely use your library with threads. We’ll also talk a bit about how to turn a function’s random integer return into an ergonomic, type-checked Result, how to translate strings and arrays to and from the world of C, and how to turn raw pointers returned from C into scoped objects with inherited lifetimes.
The overall goal of this step is to dig into the C library’s documentation and make each function’s internal assumptions explicit.
Today I want to dig into one of the difficulties we ran into while trying to rewrite our IoT Python code in Rust: specifically FFI, or the “Foreign Function Interface” — the bit that allows Rust to interact with other languages. When I tried to write Rust code to integrate with C libraries a year ago, the existing documents and guides often gave conflicting advice, and I had to stumble through the process on my own. This guide is intended to help future Rustaceans work through the process of porting C libraries to Rust, and familiarize the reader with the most common problems we encountered while doing the same.
Last December I decided to give Rust a run: I spent some time porting the C++ bits of sourmash to Rust. The main advantage here is that it's a problem I know well, so I know what the code is supposed to do and can focus on figuring out syntax and the mental model for the language. I started digging into the symbolic codebase and understanding what they did, and tried to mirror or improve it for my use cases.
View all tags