1 use std::io::stdio::println;
2
3 fn main() {
4 println("Hello!")
5 }
Rust is staticly typed, but uses type inference to make sure we don't end up with Java-style mouthfuls like 'Integer myInteger = new Integer(5)'
1 let a = 5 + 4;
2 let b = a + 2;
You can specify the type if you want, which comes in handy sometimes.
1 let c: int = 7;
Rust, in order to protect ourselves from accidental casts, makes it so you have to manually convert it
1 let a = 2;
2 /*
3 let b = a * 0.1; //Won't compile
4 */
As you can see, if we want to multiply an int by a float, we have to make sure that it typechecks.
1 let b = a as f64 * 0.1;
As Rust is designed with concurrency as a goal, everything's immutable by default.
1 let a = 1.0;
2 /*
3 a += 1.0 // Won't compile
4 */
We have to explicitly make things mutable if we want to change their value.
1 let mut a = 5;
2 a += 1;
Variables can be shadowed by defining them again
1 let a = 1;
2 let a = 5;
Everything's an expression in Rust. If you put a semicolon after it, it'll return void instead. This makes for some fun, like returning values from 'if' statements.
1 let a = if (b > 0) {
2 "Positive"
3 } else {
4 "Negative"
5 };
We also get lovely, lovely pattern matching.
1 let c = match b {
2 3 | 5 => "Lol",
3 _ => "Not lol"
4 };
Functions are one of the places you have to be explicit with types
1 //Functions must specify the types they use
2 fn increment (a: uint) -> uint {
3 a + 1
4 };
5
6 let a = 1;
7 let b = increment(a);
As you can see, the result of the last expresison is returned. You can force a return with the 'return' keyword, too.
Functions are first-class, so we can pass them in to others, and return them. Here, we're taking a tuple of three uints and a function that acts on a uint, and returning a three-tuple with that function applied to all its members
1 fn munge_triple((a, b, c): (uint, uint, uint),
2 munger: |a: uint| -> uint) -> (uint, uint, uint) {
3 (munger(a), munger(b), munger(c))
4 };
5
6 let c = munge_triple((1, 2, 3), increment);
7 //There's also shorthand
8 let c = munge_triple((1, 2, 3), |a| a+1);
Our previous function was a bit naff - we can only operate on three-tuples made of uints, and they always output the same type. There must be a better way...
In step generics to save the day!
1 fn scrunge_triple<T, U>((a, b, c): (T, T, T),
2 munger: |a: T| -> U) -> (U, U, U) {
3 (munger(a), munger(b), munger(c))
4 };
Here, we provide type parameters for the input and output, so they can be different, and we don't really care what they are as long as they match what the function we're passing wants. The compler works out what we need.
So, lets see it in action!
1 let a = scrunge_triple((1, 2, 3), |a| a + 1);
2 //Success, because T and U are both substituted with uint
3
4 let b = scrunge_triple((1, 2, 3), |a| a as f64 * 0.1);
5 //Success, because T is uint, and U is f64
6
7 //let c = munge_triple(1, 2.0, 3), |a| a * 0.1);
8
9 //Fails the typecheck as all the tuple elements
10 //were marked as the same
Rust gives us lots of lovely ways of structuring data. Enter types.
1 //We have structs, which are like their C equivalent.
2 struct Llama {
3 hairiness: uint
4 };
5
6 let jose = Llama{hairiness: 5};
7
8 //We also have tuples
9 let tuple = (5, "lol", 7);
10
11 //And a typed list-like structure called vectors
12 let my_list = ["first", "second", "third"];
13
14 //Finally, we have enums, which we'll take a closer look at later
15 enum Colors {
16 Red,
17 Blue,
18 Green
19 };
20
21 let myColor = Red;
Pointers are a key part of understanding Rust. By default, everything goes on the stack, and therefore is freed after it falls out of scope.
1 struct Kitty {
2 fluffiness: uint
3 };
4
5 fn new_kitty(fluffiness: uint) -> Kitty {
6 Kitty{fluffiness: fluffiness}
7 };
8
9 let buffcat = new_kitty(2); //On the stack
We can define Owned pointers allocated on the heap, and freed when the pointer goes out of scope
1 let tom = ~new_kitty(3);
2 //They're guaranteed unique - you can move them
3 let moved_tom = tom;
4 //The following won't compile, as it no longer lives in that slot
5 //let fluff = tom.fluffiness;
We can also create borrowed pointers, which can only legally exist while the original does, making it impossible to accidentally dereference a cleaned-up object. The compiler makes sure this is all valid.
1 let borrowed_kitty = &buffcat;
2 //We can still use the original
3 let is_the_same = borrowed_kitty.fluffiness == buffcat.fluffiness;
Algebraic data types are one of the most useful features in Rust.
1 enum MaybeInt {
2 SomeInt(int),
3 NoneInt
4 };
5
6 let maybe_number = SomeInt(5);
7 let no_number = NoneInt;
Here, we have a type that can either be boxing an int, or representing nothing. We can also use generics to make it a bit more useful.
1 enum Option<T> {
2 Some(T),
3 None
4 };
Rust doesn't allow us to use Nulls at all; they're a common source of frustration and error in languages that have them.
Instead, we use the Option type, as we've defined it (it's also a built-in, so real Rust code doesn't need to bother defining it)
1 fn safe_divide(a: int, b: int) -> Option<f64> {
2 if b == 0 {
3 None
4 } else {
5 Some(a as f64 / b as f64)
6 }
7 }
8
9 //We can then unpack them with pattern matching
10 match safe_divide(5, 0) {
11 Some(x) => "Successfully divided",
12 None => "Divide by zero"
13 };
So, let's do the classic of Functional Programming. Define a list type.
1 enum List<T> {
2 Node(T,~List<T>),
3 Terminal
4 };
It's recursively defined as data plus an owned pointer to another List, or as a terminal
So, now we can define a list!
1 let list = ~Node(1, ~Node(2, ~Node(3, ~Terminal)));
2
3 fn double_list(item: &List<uint>) -> ~List<uint> {
4 match item {
5 &Node(ref value, ref next) => {
6 ~Node(value*2, double_list(*next))
7 },
8 &Terminal => {
9 ~Terminal
10 }
11 }
12 }
13 let squared = double_list(list);
This function borrows the original list, so the original remains useful. In the pattern match, we have to specify that we're borrowing rather than moving once again, otherwise no compile.
We then have to dereference the borrowed pointer with a * otherwise it's a double borrow
But why would we hardcode what this can do? We can take a first class function, and also make it generic across all types!
1 fn map<T, U>(item: &List<T>, action: |&T| -> U) -> ~List<U> {
2 match item {
3 &Node(ref value, ref next) => {
4 ~Node(action(value), map(*next, action))
5 },
6 &Terminal => {
7 ~Terminal
8 }
9 }
10 }
11
12 //And now we can use it as normal
13 let squared = map(list, |value: &uint| { *value * *value });
Of course, Rust's standard library has already defined all of these useful things.
Rust has a lot of other awesome features that I barely understand.
Table of Contents | t |
---|---|
Exposé | ESC |
Full screen slides | e |
Presenter View | p |
Source Files | s |
Slide Numbers | n |
Toggle screen blanking | b |
Show/hide slide context | c |
Notes | 2 |
Help | h |