Am Mittwoch, 10. Februar 2016 00:54:39 UTC+1 schrieb Juha Nieminen:
> SG wrote:
> > There is a lot to like about Rust. The core language as well as the
> > standard library is well-thought-out. The tools around Rust are nice
> > (the package and dependency manager cargo, rustdoc). Unit-Testing
> > is easy and painless. The process of adding to the language is open
> > and documented. I'm excited to see where this is all going.
>
> C++ has one pragmatic advantage that most non-compatible languages
> do not: If I need a library for some specific task, there are really
> high chances that there will be a decent C (sometimes even C++) library
> out there that does exactly that. For almost any problem you might
> imagine.
Yes. This is obviously the case for every young language. I
mentioned it in the paragraph that followed the one you quoted.
> I have no idea if C libraries can be used in Rust, but if they can't,
> then that's a big point against it.
Many Rust libraries you can find are actually bindings to other C
libraries (SDL2, PortAudio, lower level async I/O, ...). Rust
supports calling C functions and Rust can expose a function with C
linkage in order to allow C code to call Rust code. But all this
happens outside of the safe subset of Rust. So, the strategy for
using some C library from Rust is to add a Rust layer on top that
exposes a safe and Rusty interface. For example, if some C library
offers an interface like this:
struct opaque_context_s;
typedef struct opaque_context_s opaque_context_t;
// might return NULL in case something went wrong
opaque_context_t* create();
void destroy(opaque_context_t*);
// warning: the returned char pointer is only valid
// until you destroy the context!!!
const char* get_name(opaque_context_t*);
This is usually wrapped in Rust RAII-style like this:
// private declarations for the C interface of the library...
enum Opaque {}
extern fn create() -> *mut Opaque;
extern fn destroy(handle: *mut Opaque);
extern fn get_name(handle: *mut Opaque) -> *const ffi::c_char;
// public Rust interface layer...
pub struct Context { // our RAII wrapper
handle: *mut Opaque,
}
impl Drop for Context { // "destructor"
fn drop(&mut self) {
unsafe { destroy(self.handle); }
}
}
impl Context {
pub fn new() -> Result<Self,()> {
let raw = unsafe { create() };
if raw.is_null() { return Err(()); }
Ok(Context { handle: raw })
}
pub fn get_name(&self) -> &ffi::CStr {
unsafe {
let raw = get_name(self.handle);
ffi::CStr::from_ptr(raw)
}
}
}
You could write something similar in C++. After all, resource
management in Rust is heavily inspired by C++. But an important
difference here is that the warning you saw in the documentation of
the C interface is actually encoded in the Rust signatures in a way
that the compiler understands them and checks for. The easy way
would have been to immediately convert the char* to an owned string
and return it by value from the get_name method to avoid lifetime
issues. But in Rust we can do better. The full return type of
get_name is actually "&'x CStr", a reference that refers to the C
string allocated by the C library constrained by a some lifetime
parameter "x" that in this case is implicitly tied to the lifetime of
the *self object by one of the lifetime elision rules of the core
language. The Rust compiler would not allow us to hold on to this
&CStr after the RAII wrapper ceases to exist. The following code
snippet that contains such a use-after-free error would not compile:
fn foo() -> Result<&ffi::CStr,()> {
let short_lived = try!(Context::new());
return short_lived.get_name();
}
This way, we provided a Rust interface that does not allow the user
create use-after-free errors while avoiding unnecessary heap
allocations for temporary string objects.
Magic! :)
sg