34 karma · joined July 14, 2014
You have to put energy into doing it, but the end result is just as much in balance with gravity as the starting point. So if you want it back where it was, you need to spent the same amount of energy to move it in exactly the same way in the opposite direction.
If Rust had defined a special struct `Str` for `&str`, then it would have to define special structs for all the combinations possible: Str, ArcSlice, BoxPath, etc.
Also the center of rotation can be controlled to be inside the passenger section. Add some seats that can automatically tilt 90 degrees and the forces might be barely noticable.
I'm not sure exactly what you mean with non-JIT platforms, As far as I know, most wasm hosts that generate native code just compile the entire wasm module at once, so its less like a JIT runtime and more like a regular compiler.
If you mean not compiling to native code at all, then you just have the performance of a plain old stack machine bytecode interpreter. Not sure how many there are currently and how well optimized they are though.
About big-endian - afaik little-endian is just the spec for storing to wasm's linear memory - the actual representation of stack values can be arbitrary (since you can not inspect their bytes directly).
C++ references on the other hand are more like modificators of a type, eg you can have a T or a T&, but having a (T&)& does not make sense. (Outside of templates, where it gets folded down to a T&.)
The magic all lies in the compiletime borrow checker, which roughly works like this:
- All data is accessed either through something on the stack or in static memory.
- Accessing data, say by creating a reference to it,
causes the compiler to "borrow" the value for the scope in which the reference
is alive.
- The references can be alive for any scope equal or smaller than for which
access to the data itself is valid.
- References track the original scope for which they are alive around as a
template-paramter-like thing called "lifetime parameter".
Note that Rusts use of the word "lifetime" is thus a bit narrower than the
one used in C++, since it just talks about stack scopes, and not the lifetime
of the actual value as would be tracked by a GC or ref counting.
Example:
let x = true;
let r = &x;
Here, r would infer to a type like `Reference<ScopeOfXVariable, bool>`.
(The actual type in rust would be a `&'a T` with
'a = scope of x, and T = bool).
- Because the scope is tracked as part of the reference type,
it is possible to copy/move/transform/wrap references safely, since
the compiler will always "know" about the original scope and thus can
check that you never end up in a situation where you accidentally outlive the
thing you borrowed, say if you try to return a type that contains a reference
somewhere deep down.
- The borrow itself acts as a compiletime read/write lock on the thing you referenced,
so for the scope that the reference is alive for the compiler prevents
you from changing or destroying the referenced thing. Example:
// This errors:
let mut a = 5;
let b = &a;
a = 10; // ERROR: a is borrowed
println!("{}", *b);
// This is fine:
let mut c = 100;
{
let d = &c;
println!("{}", *d);
}
c = 50;
- The above examples just use `&` for references, but Rust has two references types:
- &'a T, called "shared reference", which cause "shared borrows".
- &'a mut T, called "mutable references", which cause "mutable borrows".
- Both behave the same in principle, but have different restrictions and guarantees:
- A mutable borrow is exclusive, meaning no other other borrow to the same data
is allowed while the &mut T is alive, but allows you to freely change the T through
the reference.
- A shared borrow may alias, so you can have multiple &T pointing
to the same data at the same time, but you are not allowed to freely change T through
the reference.
- (If those two cases are too rigid there is also a escape hatch that
a specific type may opt-into to allow mutation of itself through a shared reference, with
exclusivity checked through some other mechanism like runtime borrow counting.)
- Through these two reference types, Rust libraries can abstract with arbitrary APIs
without loosing the borrow checker guarantees. Eg, the "reference to vector element"
example boils down as this:
let mut v = Vec::new();
v.push(1);
let r = &v[0]; // the reference in r now has a shared borrow on v.
v.push(2); // push tries to create a mutable borrow of v, which conflicts with the
borrow kept alive by r, so you get a borrow error at compiletime.
println!("{}", *r);
The important part is that all this is there, per default, for all Rust code in existence, so you can not accidentally ignore it like a library solution you might not know about, or like language features that don't know about the library solutions.For one, the build system/dependency system is much more easier to handle. Rust uses the cargo package manager, and depending on an external library is as simple as adding a single line to the Cargo.toml config file. Likewise, building, running, documenting and testing is all a build-in matter of calling `cargo [build|run|doc|test]`, which makes prototyping and getting started way easier than my experiences with C++ so far.
As far as the actual language goes it basically behaves like C++ and has a similar feature set, so you get the same sense of how given source code will end up in the compiled binary, and can write fast efficient code with, for example, features like pointers. The big distinction there is that Rust is memory safe per default, (including pointers in form of safe references) and upholds this invariant with compiletime checks and explicit language barriers (needing to encapsulate "unsafe" operations in unsafe {} blocks), so per default you are protected from whole classes of bugs that are possible in C++, like dangling pointers, memory races, iterator invalidation, etc.
- Rust as-is assumes program code/static memory is always valid/has the 'static lifetime. This assumption is implicit in the sense of function pointers, trait object vtables, static variables etc all having no lifetime bounds on something else than 'static, which means unloading their backing memory would cause unsafety.
- Thus, in order to make safe code unloading safe there would have to be a new, optional compile mode in which the compiler would treat "static" things like function pointers, trait objects, statics etc as non-'static, eg by giving them the new concrete lifetime "'crate". So if you have a "static FOO: T;" in a unloadable crate, you could only get a "&'crate T" to it, and you could only coerce to trait objects with a "+ 'crate" lifetime bound.
- 'crate would be similar to 'static in that it represents memory valid for the lifetime of the crate, but unlike 'static would not imply being always valid, and as such the borrow checker would prevent a bunch of operations for it like subtyping with any other lifetime, or usage with API's that want 'static bounds like thread spawning.
- Because 'crate is distinct from 'static, mixing unloadable and loadable code would be safe since the regular lifetime checking would ensure correct interaction between both. It means unloadable crates need to be explicitly written as such though.
- There are some major complications with generics and other compiler-generated glue code like vtables that are not fully hashed out yet: The issue there is that machine code corresponding to an upstream dependency gets compiled into the binary of the current crate, which means you would have code that typechecked with the assumptions of living in one binary be generated in another. Solutions here include banning generics and trait objects to types from extern crates, re-checking generics at instantiation location similar to C++ templates, or adding a new feature for instantiating generics outside the current crate, similar to "extern template" in C++.
As far explicitly using the symbol versioning scheme you mentioned, you can do it with an attribute today:
https://play.rust-lang.org/?gist=92be155702d53c6411b2&versio...
(Disclaimer: I haven't actually compiled this as a dynamic library and checked, but its what that attribute is for, and the names seem to be used verbatim in the ASM output)
The difference being that in your code you can only put in a single type at a time, eg [Int] or [String], but not both Int and String under the common Show interface type.
You can definitely create leaking cycles with both though, but Rc and Arc also support Weak pointers to safely break up cyclic ownership trees.