pull/10/head
Alexis Beingessner 10 years ago committed by Manish Goregaokar
parent f919553aa9
commit 91fd911c42

@ -0,0 +1,45 @@
% Interfacing with other Languages (FFI)
*Obviously* we'd all love to live in a **glorious** world where everything is
written in Rust, Rust, and More Rust. Tragically, programs have been written
in Not Rust for over 50 years. Crufty enterprises are doomed to
support ancient code bases, and greybeard programmers stuck in their ways
*insist* on writing programs in other languages, even to this day!
In all seriousness, there's a myriad of reasons for your codebase to be a
hybrid of different languages, and Rust is well-designed to interface with
all of them as painlessly as possible. It does this through the tried and
true strategy of all languages: pretend to be C, and understand C.
Thanks to Rust's minimal runtime and C-like semantics, this is about as
painless as FFI with C++. Obviously, most of Rust's features are completely
incompatible with other languages: tagged unions, zero-sized-types, dynamically-
sized types, destructors, methods, traits, references, and lifetimes are all
concepts that you won't be able to expose or accept in your foreign function
interface.
All mapping through C will give you is functions, structs, globals, raw pointers,
and C-like enums. That's it. Rust's default data layouts are also incompatible
with the C layout. See [the section on data layout][data.html] for details.
Long story short: mark FFI structs and enums with `#[repr(C)]`, mark FFI
functions as `extern`.
## Runtime
Rust's runtime is sufficiently minimal that it requires *no* special handling.
You don't need to set anything up. You don't need to tear anything down.
Awesome.
The only runtime detail you *really* need to worry about is unwinding. Rust's
unwinding model is not defined to be incompatible with any particular language.
That means that if you call Rust from another language and it unwinds into the
calling language, this will cause Undefined Behaviour. Similarly, if another
language unwinds into Rust, it will also cause Undefined Behaviour.
Rust can't really do anything about other languages unwinding into it (FFI is unsafe
for a reason!), but you can be a good FFI citizen by catching panics in any
FFI functions you export. Rust provides `thread::catch_panic` for exactly this.
Unfortunately, this API is still unstable.
## libc

@ -60,6 +60,7 @@ read-write lock.
## Lifetimes
Rust's static checks are managed by the *borrow checker* (borrowck), which tracks
@ -219,6 +220,77 @@ these are unstable due to their awkward nature and questionable utility.
## Higher-Rank Lifetimes
Generics in Rust generally allow types to be instantiated with arbitrary
associated lifetimes, but this fixes the lifetimes they work with once
instantiated. For almost all types, this is exactly the desired behaviour.
For example slice::Iter can work with arbitrary lifetimes, determined by the
slice that instantiates it. However *once* Iter is instantiated the lifetimes
it works with cannot be changed. It returns references that live for some
particular `'a`.
However some types are more flexible than this. In particular, a single
instantiation of a function can process arbitrary lifetimes:
```rust
fn identity(input: &u8) -> &u8 { input }
```
What is *the* lifetime that identity works with? There is none. If you think
this is "cheating" because functions are statically instantiated, then you need
only consider the equivalent closure:
```rust
let identity = |input: &u8| input;
```
These functions are *higher ranked* over the lifetimes they work with. This means
that they're generic over what they handle *after instantiation*. For most things
this would pose a massive problem, but because lifetimes don't *exist* at runtime,
this is really just a compile-time mechanism. The Fn traits contain sugar that
allows higher-rank lifetimes to simply be expressed by simply omitting lifetimes:
```rust
fn main() {
foo(|input| input);
}
fn foo<F>(f: F)
// F is higher-ranked over the lifetime these references have
where F: Fn(&u8) -> &u8
{
f(&0);
f(&1);
}
```
The desugaring of this is actually unstable:
```
#![feature(unboxed_closures)]
fn main() {
foo(|input| input);
}
fn foo<F>(f: F)
where F: for<'a> Fn<(&'a u8,), Output=&'a u8>
{
f(&0);
f(&1);
}
```
`for<'a>` is how we declare a higher-ranked lifetime. Unfortunately higher-ranked
lifetimes are still fairly new, and are missing a few features to make them
maximally useful outside of the Fn traits.
## Subtyping and Variance
Although Rust doesn't have any notion of inheritance, it *does* include subtyping.
@ -227,12 +299,15 @@ from scopes, we can partially order them based on an *outlives* relationship. We
can even express this as a generic bound: `T: 'a` specifies that `T` *outlives* `'a`.
We can then define subtyping on lifetimes in terms of lifetimes: `'a : 'b` implies
`'a <: b` -- if `'a' outlives `'b`, then `'a` is a subtype of `'b`. This is a very
`'a <: b` -- if `'a` outlives `'b`, then `'a` is a subtype of `'b`. This is a very
large source of confusion, because a bigger scope is a *sub type* of a smaller scope.
This does in fact make sense. The intuitive reason for this is that if you expect an
`&'a u8`, then it's totally fine for me to hand you an `&'static u8`, in the same way
that if you expect an Animal in Java, it's totally fine for me to hand you a Cat.
(Note, the subtyping relationship and typed-ness of lifetimes is a fairly arbitrary
construct that some disagree with. I just find that it simplifies this analysis.)
Variance is where things get really harsh.
Variance is a property that *type constructors* have. A type constructor in Rust
@ -278,7 +353,7 @@ fn overwrite<T: Copy>(input: &mut T, new: &mut T) {
The signature of `overwrite` is clearly valid: it takes mutable references to two values
of the same type, and replaces one with the other. We have seen already that `&` is
covariant, and `'static` is a subtype of *any* `'a', so `&'static str` is a
covariant, and `'static` is a subtype of *any* `'a`, so `&'static str` is a
subtype of `&'a str`. Therefore, if `&mut` was
*also* covariant, then the lifetime of the `&'static str` would successfully be
"shrunk" down to the shorter lifetime of the string, and `replace` would be
@ -341,8 +416,16 @@ respectively.
## PhantomData and PhantomFn
This is all well and good for the types the standard library provides, but
how is variance determined for type that *you* define? The variance of a type
over its generic arguments is determined by how they're stored.
how is variance determined for type that *you* define? A struct is, informally
speaking, covariant over all its fields (and an enum over its variants). This
basically means that it inherits the variance of its fields. If a struct `Foo`
has a generic argument `A` that is used in a field `a`, then Foo's variance
over `A` is exactly `a`'s variance. However this is complicated if `A` is used
in multiple fields.
* If all uses of A are covariant, then Foo is covariant over A
* If all uses of A are contravariant, then Foo is contravariant over A
* Otherwise, Foo is invariant over A
```rust
struct Foo<'a, 'b, A, B, C, D, E, F, G, H> {
@ -360,7 +443,7 @@ struct Foo<'a, 'b, A, B, C, D, E, F, G, H> {
However when working with unsafe code, we can often end up in a situation where
types or lifetimes are logically associated with a struct, but not actually
reachable. This most commonly occurs with lifetimes. For instance, the `Iter`
part of a field. This most commonly occurs with lifetimes. For instance, the `Iter`
for `&'a [T]` is (approximately) defined as follows:
```

@ -0,0 +1,2 @@
% Eliminating the Runtime (no_std)
Loading…
Cancel
Save