I've been working on Rust bindings for a C SDK recently, and the Rust wrapper code was far more complex than the C code it wrapped. I ended up ceding and getting reasonable wrappers by limiting how it can be used, instead of moddeling the C API's full capabilities. There are certainly sound, reasonable models of memory ownership that are difficult or impossible to express with Rust's ownership model.
Sure, a different model that was easy to model would have been picked if we were initially using Rust, but we were not, and the existing model in C is what we need to wrap. Also, a more Rust-friendly model would have incured higher memory costs.
I totally hear you about C friendly APIs not making sense in rust. GTK struggles with this - I tried to make a native UI in rust a few months ago using gtk and it was horrible.
> Also, a more Rust-friendly model would have incured higher memory costs.
Can you give some details? This hasn’t been my experience.
I find in general rust’s ownership model pushes me toward designs where all my structs are in a strict tree, which is very efficient in memory since everything is packed in memory. In comparison, most C++ APIs I’ve used make a nest of objects with pointers going everywhere. And this style is less memory efficient, and less performant because of page faults.
C has access to a couple tricks safe rust is missing. But on the flip side, C’s lack of generics means lots of programs roll their own hash tables, array lists and various other tools. And they’re often either dynamically typed (and horribly inefficient as a result) or they do macro tricks with type parameters - and that’s ugly as sin. A lack of generics and monomorphization means C programs usually have slightly smaller binaries. But they often don’t run as fast. That’s often a bad trade on modern computers.
The code is written in an embedded style, i.e. no dynamic memory allocation or thread creation/deletion after program initialization. It's also prioritizing reducing memory usage over performance since we are targeting memory constrained devices (and our performance target is 10 tps and we have like 100k tps). Thus we'd use trait objects over monomorphization. Dynamic collections are also off the table unless backed by a fixed-size arena on the stack or static mem.
We heavily use arenas. We also have runtime-typed objects used to represent dynamically typed data like that obtained from JSON/Yaml or over IPC. If we were to be more friendly to modeling in Rust, we'd likely require that all memory reachable from an object node be in the same arena, disallowing common patterns like having list/map's arrays in one arena and having keys/strings in another or in static mem (this allows reusing other buffers without forcing copying all the data, so backing arrays can be smaller).
This is ultimately a good example of how to use Rust. You express as much of the API as you can safely, and the rest is unsafe. APIs that can't be safely modelled can still be exposed to Rust & marked unsafe, if they're needed
I've been working on Rust bindings for a C SDK recently, and the Rust wrapper code was far more complex than the C code it wrapped. I ended up ceding and getting reasonable wrappers by limiting how it can be used, instead of moddeling the C API's full capabilities. There are certainly sound, reasonable models of memory ownership that are difficult or impossible to express with Rust's ownership model.
Sure, a different model that was easy to model would have been picked if we were initially using Rust, but we were not, and the existing model in C is what we need to wrap. Also, a more Rust-friendly model would have incured higher memory costs.
I totally hear you about C friendly APIs not making sense in rust. GTK struggles with this - I tried to make a native UI in rust a few months ago using gtk and it was horrible.
> Also, a more Rust-friendly model would have incured higher memory costs.
Can you give some details? This hasn’t been my experience.
I find in general rust’s ownership model pushes me toward designs where all my structs are in a strict tree, which is very efficient in memory since everything is packed in memory. In comparison, most C++ APIs I’ve used make a nest of objects with pointers going everywhere. And this style is less memory efficient, and less performant because of page faults.
C has access to a couple tricks safe rust is missing. But on the flip side, C’s lack of generics means lots of programs roll their own hash tables, array lists and various other tools. And they’re often either dynamically typed (and horribly inefficient as a result) or they do macro tricks with type parameters - and that’s ugly as sin. A lack of generics and monomorphization means C programs usually have slightly smaller binaries. But they often don’t run as fast. That’s often a bad trade on modern computers.
The code is written in an embedded style, i.e. no dynamic memory allocation or thread creation/deletion after program initialization. It's also prioritizing reducing memory usage over performance since we are targeting memory constrained devices (and our performance target is 10 tps and we have like 100k tps). Thus we'd use trait objects over monomorphization. Dynamic collections are also off the table unless backed by a fixed-size arena on the stack or static mem.
We heavily use arenas. We also have runtime-typed objects used to represent dynamically typed data like that obtained from JSON/Yaml or over IPC. If we were to be more friendly to modeling in Rust, we'd likely require that all memory reachable from an object node be in the same arena, disallowing common patterns like having list/map's arrays in one arena and having keys/strings in another or in static mem (this allows reusing other buffers without forcing copying all the data, so backing arrays can be smaller).
2 replies →
This is ultimately a good example of how to use Rust. You express as much of the API as you can safely, and the rest is unsafe. APIs that can't be safely modelled can still be exposed to Rust & marked unsafe, if they're needed
That works for functions. For datatypes that are used throughout the API, it does not work so well.
2 replies →
Which SDK? I've only written Rust FFI to pretty basic C APIs. I'm curious to get a sense of the limitations on something more complex