Rust, a fairly new programming language promises to be fast and secure. The following blog entry discusses how Rust tries to achieve these two goals.
The key concept is that every resource always belongs to exactly one variable. More precisely one lifetime, which is normally automatically created on variable creation. The concept of lifetimes would go into too much detail, so in this blog entry we assume that lifetimes and variables are the same. If you want to learn more about lifetimes or any other topic of Rust, you can read “the book” – the main resource when learning Rust: https://doc.rust-lang.org/book/
If the programmer creates a new resource, for example a vector (which is a dynamic growable array) this vector always belongs to one variable. The following line creates a new vector in Rust:
let v = vec![1, 2, 3, 4];
If we would assign this vector to another variable then the vector is “moved”, which means that the original variable isn’t usable anymore. The following lines would compile:
let v = vec![1, 2, 3, 4]; let v2 = v;
However, the next lines would not:
let v = vec![1, 2, 3, 4]; let v2 = v; v.push(5);
Because the vector was moved from v to v2, the variable v is not usable anymore. There are two different behaviors in Rust when the programmer assigns a variable to another:
- moving
- The variable which was assigned from is not usable anymore.
- copy
- All data in the variable is copied to another variable.
The default behavior is moving. If a type implements the special trait Copy (which is like a Java interface), then the type is copied instead. A Vector does not implement this trait which means that a vector is always moved. It does not implement Copy because it is not certain how large the amount of data is inside the vector. If it would have a gigabyte of data, then copying would take a lot of performance which maybe would not be expected by the programmer.
Both of these behaviors enforce the key concept of Rust: After assigning a resource to a variable this resource always belongs to one variable.
Because of this concept, Rust can always free/delete/remove a resource once the variable which it belongs to runs out of scope.
{ //<----- Scope starts let v = vec![1, 2, 3, 4]; //Some Code } //<------ Scope ends, v is deleted
Rust is able to automatically determine when a variable is not used anymore during compile time. This means that the language does not need any garbage collector which is a huge performance boost in real time applications. Other programming languages with garbage collectors could sometimes pause the whole application because the garbage collector needs to be run.
This concept also makes multithreading more safe because a variable always has to belong to one thread. This permits race conditions.
When a variable is given to a function it is also moved/copied. This means that the variable would not be usable anymore if the Copy trait is not implemented and it is given to a function. This behavior would not be sufficient for real applications. Therefore, Rust had to implement another way to share data between scopes/functions. This technique is called borrowing.
Borrows are essentially references with extra rules. Every variable in Rust has two possible mutability settings: mutable and immutable. Mutable means that the variable can be changed, immutable means that it cannot be changed (like java final variables). This also applies to borrows, a borrow can be mutable or immutable.
To create an immutable borrow you’d write:
let mut v = 5; //creates a new mutable variable { let vref = &v; //borrows the value of v as immutable reference println!("{}", *vref); //prints the value 5 to console }
And to create a mutable borrow:
let mut v = 5; //creates a new mutable variable { let vmutref = &mut v; //borrows the value of v as mutable reference *vmutref = 6; //changes the value of v println!("{}", *vmutref); //prints the value 6 to console }
These borrows should raise several questions in your mind. If we have borrows, doesn’t this destroy the key concept that a resource always has to belong to one variable? What about race conditions? When should Rust free a resource if there are maybe some borrows left to the resource? Remember: there is no garbage collections and normally no reference counting.
These questions are solved once you understand the various rules of borrows. After all, borrows are not called references. This is because there are rules which don’t apply to references of languages like Java.
There are two important rules for borrows:
- Exactly one (not both!) of the following rules have to be true for any scope:
- There is exactly one mutable borrow
- There are one or more immutable borrows
This rule permits race conditions. If there is only one borrow which is able to change the value and no other borrows then there can`t be any race conditions. The same applies to the second sentence. If there is no borrow which possibly changes the value, then there can be several immutable borrows which are only able to read the value. Read only never leads to race conditions.
- All borrows must leave their scope before the owning variable leaves its scope.
This rule enforces that Rust is still able to know when it should free a resource. Because there can’t be any borrows left once the resource gets freed there is no possibility for use-after-free errors.
These rules make sure that the key concept of Rust is not broken. A resource still belongs to only one variable and if that variable runs out of scope the resource can be freed safely. Note that there are other safety concepts in Rust, which can be read about in “the book”: https://doc.rust-lang.org/book/
In conclusion, Rust is fast because it has no garbage collection overhead and it is safe because multithreading is handled in a safe way as well as variable deletion.
Research question:
“Which big projects use Rust?”
There are whole projects which use Rust exclusively, as well as projects which start to replace some of their components with components written in Rust.
There are several big companies that support Rust. Mozilla, Samsung, GitHub, Dropbox… just to name a few.
Mozilla was an early adopter of the programming language Rust. Security is a valuable goal of web browser developers. In this year (2016) Mozilla started to reprogram several of Firefox’s components with Rust and to deploy them in their releases. Meanwhile Samsung and Mozilla are working on a completely new rendering engine which uses Rust exclusively (“Servo”) for rendering web layouts.
Dropbox started to reprogram several components of their backend systems which handle many petabytes of data every week.
Rust is a low level programming language – that’s the reason why it’s possible to program whole operating systems in rust. “Redox OS” proves this.
“Piston” is a Game Engine, written exclusively in Rust.
This list is not complete. Still it should give you a good impression of how fast Rust is growing.
Other research questions, without answers yet:
“How fast will Rust grow in popularity?”
“How safe is rust really? Rust does not provide 100% safety. When will there be security holes in projects which are written exclusively in Rust or in its Rust parts?”
“Which programming language will use the concepts of Rust and adopt them?”
“Will there ever be a Rust++-kind-of-language which supports all parts of object oriented programming?”
“Rust vs Go vs D vs ??? – which language will replace C/C++ the most?”
Leave a Reply
You must be logged in to post a comment.