Rust Me, I'm a Developer!

Rust Me, I'm a Developer!

A brief overview of Rust. Currently for Rust v0.10

Ee9f9502f703ce754aa7feda9eefc579?s=128

Greg Malcolm

April 04, 2014
Tweet

Transcript

  1. 2.

    True Facts ! About Me gregmalcolm speakerdeck: gregmalcolm/rust-me-im-a-developer Blah blah

    blah, my name is Greg Malcolm. Currently I’m working with Ruby on Rails and Javascript. The job before that I worked with embedded C++. The job before that I worked with .NET and Java. And so on. See the pattern? I enjoy trying out new languages. And my quest for the new and shiny has lead me to…
  2. 3.

    True Facts ! About Me Not from Wisconsin… gregmalcolm speakerdeck:

    gregmalcolm/rust-me-im-a-developer Do they drink tea in Peru? Nah, Australian right? Blah blah blah, my name is Greg Malcolm. Currently I’m working with Ruby on Rails and Javascript. The job before that I worked with embedded C++. The job before that I worked with .NET and Java. And so on. See the pattern? I enjoy trying out new languages. And my quest for the new and shiny has lead me to…
  3. 4.

    Rust is currently on version 0.10. Until we get to

    version 1.0 the code is subject to radical change. That said things are starting to calm down a lot, and version 1.0 should be coming out in the next year.
  4. 5.

    An alternative to C and C++ RUST is a “Systems

    Programming Language”. It’s for writing software that serves the platform rather than the application layer; So it’s competing with the likes of C and C++. ! Let’s talk about C and C++ for a moment. The upside of working with C and C++ is it’s really fast and efficient and really takes you close to the metal. It allows you to address the computers memory systems directly and will compile natively on any operating system. It’s as good as it gets without resorting to assembly language programming which can be difficult to maintain. It also stays clear of using garbage collection, which drains resources and causes periodic slowdowns. However, there are downsides, one of those being that it’s really easy to accidentally introduce bugs that blow up intermittently and when you least expect it. Tracking down these problems can be very tricky. More on that in a bit.
  5. 6.

    use std::io::timer::sleep; ! // Rust 0.10 fn main() { let

    actions = [("Captain America", "bashes", 20), ("Black Widow", "slashes", 25), ("Ironman", "throws cash at", 0), ("Hulk", "SMASHES", 200)]; ! let mut outcomes = actions.iter() .map(|&action| { let (hero, attack, damage) = action; format!("{:s} {:s} Red Skull for {:d} damage", hero, attack, damage) }); ! for outcome in outcomes { spawn(proc() { sleep(500); println!(“{:s}”, outcome); }); } } Rust sample Lets take a look at a little sample as a taster… ! In this scenario the Avengers have just rolled for initiative in a battle against Red Skull we want to show the results. ! The actions are declared in the form of a vector of tuples. Rust calls arrays “vectors”, much like C++. Each tuple is made up of a hero string, an attack string and an integer value giving damage. ! Next we need to figure out the outcome of each action to output. We iterate though each action tuple transforming the tuple into a string using the map() method. Ruby developers will notice the inside of map() looks similar to a Ruby block. This is called a closure in Rust. Inside the closure we’ll decode the tuple contents to hero, attack and damage variables. format!() will create a sanitized string output replacing the {:s}, {:s} and {:d} with hero, attack and damage. ! Finally in the for loop at the end we’ll iterate through each outcome string and display it. We’re wrapping the println!() statement in a spawn() block to make each output happen asynchronously in it’s own lightweight thread. Rust is really good at concurrency and multithreading, we’ll talk about that later. The sleep() statement helps induce the 4 outcomes to display in random order.
  6. 7.

    Rust sample $ rustc intro.rs ! $ ./intro ! Captain

    America bashes Red Skull for 20 damage! Ironman throws cash at Red Skull for 0 damage! Black Widow slashes Red Skull for 25 damage! Hulk SMASHES Red Skull for 200 damage! $ ./intro! Black Widow slashes Red Skull for 25 damage! Captain America bashes Red Skull for 20 damage! Ironman throws cash at Red Skull for 0 damage! Hulk SMASHES Red Skull for 200 damage! $ I compile the sample using the compiler, rustc. This will give me a native binary that I can execute.
  7. 8.

    Consider browsers… Let’s talk about browsers. Browsers need to be

    extremely fast. Therefore all the major browsers make use of a lot of C/C++ code. For example I think read somewhere that Mozilla uses something like 7 million lines of C and C++. When you’ve got that much C++ code safety is a problem. So much so that Mozilla have started funding Rust as a full time research project as an alternative language providing speed, efficiency AND safety. They are currently using it to build a new experimental browser, called…
  8. 10.

    int main() { char *str = "BOOM"; str[0] = 'Z';

    ! return 0; } C Boom! Let’s take a look at life in C. Anyone guess what this code does? Well, it look’s like it’s trying to change the word BOOM into ZOOM. Only problem is that we’ve initialized it to the string “ZOOM” which actually makes it a constant. Does the compiler tell you? No.
  9. 11.

    int main() { char *str = "BOOM"; str[0] = 'Z';

    ! return 0; } C $ clang boom.c -o boom $ Boom! Let’s take a look at life in C. Anyone guess what this code does? Well, it look’s like it’s trying to change the word BOOM into ZOOM. Only problem is that we’ve initialized it to the string “ZOOM” which actually makes it a constant. Does the compiler tell you? No.
  10. 12.

    int main() { char *str = "BOOM"; str[0] = 'Z';

    ! return 0; } C $ clang boom.c -o boom ! $ ./boom ! [1] 38116 bus error ./boom! $ Boom! However when we do see a problem at runtime. It turns out that when a string is defined this way, it’s actually a constant string. Therefore changing out the first character causes explosions.
  11. 13.

    int main() { char *str = "BOOM"; str[0] = 'Z';

    ! return 0; } C $ clang boom.c -o boom ! $ ./boom ! [1] 38116 bus error ./boom! $ Boom! However when we do see a problem at runtime. It turns out that when a string is defined this way, it’s actually a constant string. Therefore changing out the first character causes explosions.
  12. 14.

    Boom! fn main() { let s = ~"BOOM"; s.shift_char(); s.unshift_char('Z');

    ! println!("It go {:s}", s); } Rust Here is a similar version of the code written in Rust. Putting a “~” in front of the string makes it an “owned pointer” to a string on the heap which we need to do if we want a changeable string. Don’t worry about that too much for now. We use shift_char() to remove the ‘B’, and unshift_char() to prepend a ‘Z’ in it’s place. ! Rust warns you about problems at compile time rather than runtime. Compiling this program exposes a problem: “cannot borrow immutable local variable as mutable”. Turns out that Rust actually defines immutable constants by default. This is advantageous for optimization and concurrency concerns.
  13. 15.

    Boom! fn main() { let s = ~"BOOM"; s.shift_char(); s.unshift_char('Z');

    ! println!("It go {:s}", s); } $ rustc less_boom.rs less_boom.rs:3:5: 3:6 error: cannot borrow immutable local variable as mutable less_boom.rs:3 s.shift_char(); ^ less_boom.rs:4:5: 4:6 error: cannot borrow immutable local variable as mutable less_boom.rs:4 s.unshift_char('Z'); ^ Rust Here is a similar version of the code written in Rust. Putting a “~” in front of the string makes it an “owned pointer” to a string on the heap which we need to do if we want a changeable string. Don’t worry about that too much for now. We use shift_char() to remove the ‘B’, and unshift_char() to prepend a ‘Z’ in it’s place. ! Rust warns you about problems at compile time rather than runtime. Compiling this program exposes a problem: “cannot borrow immutable local variable as mutable”. Turns out that Rust actually defines immutable constants by default. This is advantageous for optimization and concurrency concerns.
  14. 16.

    Boom! fn main() { let mut s = ~"BOOM"; s.shift_char();

    s.unshift_char('Z'); ! println!("It go {:s}", s); } Rust We just need to add “mut” to the declaration to make the variable mutable. Fixed!
  15. 17.

    Boom! fn main() { let mut s = ~"BOOM"; s.shift_char();

    s.unshift_char('Z'); ! println!("It go {:s}", s); } $ rustc less_boom.rs $ Rust We just need to add “mut” to the declaration to make the variable mutable. Fixed!
  16. 18.

    Boom! fn main() { let mut s = ~"BOOM"; s.shift_char();

    s.unshift_char('Z'); ! println!("It go {:s}", s); } $ rustc less_boom.rs! $ ./less_boom! It go ZOOM! $ Rust And it executes too.
  17. 19.

    #include <stdio.h> #include <stdbool.h> int main() { int *dangler =

    NULL; if (true) { int temp=23; dangler = &temp; } printf("Dangler equals: %d \n", *dangler); ! return 1; } Dangling Pointers C That last example was a little unfair. C++ would have handled it more gracefully. Dangling pointers however are a very serious issue in C and C++. Let’s take a look at how this sample works…
  18. 20.

    #include <stdio.h> #include <stdbool.h> int main() { int *dangler =

    NULL; if (true) { int temp=23; dangler = &temp; } printf("Dangler equals: %d \n", *dangler); ! return 1; } Dangling Pointers C ‘dangler’ is defined as pointer to an integer. It is located at memory address 0x1E00. ! Next ‘dangler’ is set to point to the variable ‘temp’ at memory location 0x1E01. If dereferenced it will return the value 23. ! However, dangler is dereferenced on the printf statement after the temp variable goes out of scope. What will happen when we reach this line?
  19. 21.

    #include <stdio.h> #include <stdbool.h> int main() { int *dangler =

    NULL; if (true) { int temp=23; dangler = &temp; } printf("Dangler equals: %d \n", *dangler); ! return 1; } Dangling Pointers C The Stack 0x1E00 dangler ! ! =NULL ‘dangler’ is defined as pointer to an integer. It is located at memory address 0x1E00. ! Next ‘dangler’ is set to point to the variable ‘temp’ at memory location 0x1E01. If dereferenced it will return the value 23. ! However, dangler is dereferenced on the printf statement after the temp variable goes out of scope. What will happen when we reach this line?
  20. 22.

    #include <stdio.h> #include <stdbool.h> int main() { int *dangler =

    NULL; if (true) { int temp=23; dangler = &temp; } printf("Dangler equals: %d \n", *dangler); ! return 1; } Dangling Pointers C The Stack 0x1E00 dangler 0x1E01 temp =23 =&0x1E01 (23) ‘dangler’ is defined as pointer to an integer. It is located at memory address 0x1E00. ! Next ‘dangler’ is set to point to the variable ‘temp’ at memory location 0x1E01. If dereferenced it will return the value 23. ! However, dangler is dereferenced on the printf statement after the temp variable goes out of scope. What will happen when we reach this line?
  21. 23.

    #include <stdio.h> #include <stdbool.h> int main() { int *dangler =

    NULL; if (true) { int temp=23; dangler = &temp; } printf("Dangler equals: %d \n", *dangler); ! return 1; } Dangling Pointers C The Stack 0x1E00 dangler=&0x1E01 (?) ! 0x1E01 temp =23 ‘dangler’ is defined as pointer to an integer. It is located at memory address 0x1E00. ! Next ‘dangler’ is set to point to the variable ‘temp’ at memory location 0x1E01. If dereferenced it will return the value 23. ! However, dangler is dereferenced on the printf statement after the temp variable goes out of scope. What will happen when we reach this line?
  22. 24.

    #include <stdio.h> #include <stdbool.h> int main() { int *dangler =

    NULL; if (true) { int temp=23; dangler = &temp; } printf("Dangler equals: %d \n", *dangler); ! return 1; } $ clang dangle.c -o dangle $ Dangling Pointers C ‘dangler’ is defined as pointer to an integer. It is located at memory address 0x1E00. ! Next ‘dangler’ is set to point to the variable ‘temp’ at memory location 0x1E01. If dereferenced it will return the value 23. ! However, dangler is dereferenced on the printf statement after the temp variable goes out of scope. What will happen when we reach this line?
  23. 25.

    #include <stdio.h> #include <stdbool.h> int main() { int *dangler =

    NULL; if (true) { int temp=23; dangler = &temp; } printf("Dangler equals: %d \n", *dangler); ! return 1; } Dangling Pointers C The answer is “undetermined”. This time around we actually get back 23 because the memory hasn’t been reallocated yet. But what if the memory had been reallocated? Maybe 9 times out of 10 this line will return the expected result in production. And on the other occasion crash the program. What if you changed the value referenced at this location? You could be changing memory in some other part of the application. Could get crazy! These kinds of problems can spot during testing, let alone troubleshooting and fixing.
  24. 26.

    #include <stdio.h> #include <stdbool.h> int main() { int *dangler =

    NULL; if (true) { int temp=23; dangler = &temp; } printf("Dangler equals: %d \n", *dangler); ! return 1; } $ clang dangle.c -o dangle! $ ./dangle ! Dangler equals: 23 ! $ Dangling Pointers C The answer is “undetermined”. This time around we actually get back 23 because the memory hasn’t been reallocated yet. But what if the memory had been reallocated? Maybe 9 times out of 10 this line will return the expected result in production. And on the other occasion crash the program. What if you changed the value referenced at this location? You could be changing memory in some other part of the application. Could get crazy! These kinds of problems can spot during testing, let alone troubleshooting and fixing.
  25. 27.

    fn main() { let dangler; if true { let temp

    = 23; dangler = &temp; } println!("Dangler equals: {:d}", *dangler); } Rust Dangling Pointers Not a problem in Rust. It’ll warn you at compile time that you’re doing something hazardous. Let’s try compiling this.
  26. 28.

    fn main() { let dangler; if true { let temp

    = 23; dangler = &temp; } println!("Dangler equals: {:d}", *dangler); } $ rustc less_dangle.rs less_dangle.rs:5:19: 5:24 error: `temp` does not live long enough less_dangle.rs:5 dangler = &temp; ^~~~~ less_dangle.rs:1:11: 8:2 note: reference must be valid for the block at 1:10... less_dangle.rs:1 fn main() { less_dangle.rs:2 let dangler; less_dangle.rs:3 if (true) { less_dangle.rs:4 let temp = 23; less_dangle.rs:5 dangler = &temp; less_dangle.rs:6 } ... less_dangle.rs:3:15: 6:6 note: ...but borrowed value is only valid for the block at less_dangle.rs:3 if (true) { Rust Dangling Pointers Not a problem in Rust. It’ll warn you at compile time that you’re doing something hazardous. Let’s try compiling this.
  27. 29.

    fn main() { let dangler; if true { let temp

    = 23; dangler = &temp; } println!("Dangler equals: {:d}", *dangler); } less_dangle.rs:7:39: 7:46 error: use of possibly uninitialized variable: `dangler`! less_dangle.rs:7 println!("Dangler equals: {:d}", *dangler);! ^~~~~~~ Rust Dangling Pointers First problem: it doesn’t like that we’re potentially initializing dangler in an inconsistent way.
  28. 30.

    fn main() { let dangler; if true { let temp

    = 23; dangler = &temp; } else { // dangler isn’t defined for this case! } println!("Dangler equals: {:d}", *dangler); } Dangling Pointers Rust less_dangle.rs:7:39: 7:46 error: use of possibly uninitialized variable: `dangler`! less_dangle.rs:7 println!("Dangler equals: {:d}", *dangler);! ^~~~~~~ Ok, this one is one me. By declaring dangler on the outside of the if statement and then defining it inside I’ve made the definition ambiguous. What if there was an else cause? It could be defined as something else entirely.
  29. 31.

    fn main() { let dangler; { let temp = 23;

    dangler = &temp; } println!("Dangler equals: {:d}", *dangler); } less_dangle.rs:5:19: 5:24 error: `temp` does not live long enough! less_dangle.rs:5 dangler = &temp;! ^~~~~! less_dangle.rs:1:11: 8:2 note: reference must be valid for the block at 1:10! ...! less_dangle.rs:3:15: 6:6 note: ...but borrowed value is only valid for the block at 3:14! less_dangle.rs:3 if (true) {! less_dangle.rs:4 let temp = 23;! less_dangle.rs:5 dangler = &temp;! less_dangle.rs:6 } Dangling Pointers Rust No problem. We don’t really need an if statement, let’s just use empty braces instead. ! Next up: “temp does not live long enough”. Yup! We used dangler after it “died”. ! But what’s this? “Borrowed value does not live long enough”? What does that mean?
  30. 32.

    fn main() { let dangler; { let temp = 23;

    dangler = &temp; } println!("Dangler equals: {:d}", *dangler); } less_dangle.rs:5:19: 5:24 error: `temp` does not live long enough! less_dangle.rs:5 dangler = &temp;! ^~~~~! less_dangle.rs:1:11: 8:2 note: reference must be valid for the block at 1:10! ...! less_dangle.rs:3:15: 6:6 note: ...but borrowed value is only valid for the block at 3:14! less_dangle.rs:3 if (true) {! less_dangle.rs:4 let temp = 23;! less_dangle.rs:5 dangler = &temp;! less_dangle.rs:6 } Dangling Pointers Rust No problem. We don’t really need an if statement, let’s just use empty braces instead. ! Next up: “temp does not live long enough”. Yup! We used dangler after it “died”. ! But what’s this? “Borrowed value does not live long enough”? What does that mean?
  31. 33.

    Borrowing (&) let shield = Shield::new(); 1) For this scenario

    Captain America has created a shield so he can fight crime. 2) But Stephen Colbert wants to fight crime too! 3) Stephen Colbert borrows the shield by dereferencing the shield variable. Stephen now has the shield so Captain America can’t use it while he has it. 4) But because this is called “borrowing”, Stephen eventually has to return the shield. Now Captain America can now go back to fighting crime,and Stephen Colbert can go back to feeling sad.
  32. 34.

    Borrowing (&) let shield = Shield::new(); 1) For this scenario

    Captain America has created a shield so he can fight crime. 2) But Stephen Colbert wants to fight crime too! 3) Stephen Colbert borrows the shield by dereferencing the shield variable. Stephen now has the shield so Captain America can’t use it while he has it. 4) But because this is called “borrowing”, Stephen eventually has to return the shield. Now Captain America can now go back to fighting crime,and Stephen Colbert can go back to feeling sad.
  33. 35.

    Borrowing (&) let colbert = &shield; 1) For this scenario

    Captain America has created a shield so he can fight crime. 2) But Stephen Colbert wants to fight crime too! 3) Stephen Colbert borrows the shield by dereferencing the shield variable. Stephen now has the shield so Captain America can’t use it while he has it. 4) But because this is called “borrowing”, Stephen eventually has to return the shield. Now Captain America can now go back to fighting crime,and Stephen Colbert can go back to feeling sad.
  34. 36.

    Borrowing (&) 1) For this scenario Captain America has created

    a shield so he can fight crime. 2) But Stephen Colbert wants to fight crime too! 3) Stephen Colbert borrows the shield by dereferencing the shield variable. Stephen now has the shield so Captain America can’t use it while he has it. 4) But because this is called “borrowing”, Stephen eventually has to return the shield. Now Captain America can now go back to fighting crime,and Stephen Colbert can go back to feeling sad.
  35. 37.

    fn main() { let dangler; { let temp = 23;

    dangler = &temp; } println!("Dangler equals: {:d}", *dangler); } Rust Dangling Pointers less_dangle.rs:5:19: 5:24 error: `temp` does not live long enough! less_dangle.rs:5 dangler = &temp;! ^~~~~! less_dangle.rs:1:11: 8:2 note: reference must be valid for the block at 1:10! ...! less_dangle.rs:3:15: 6:6 note: ...but borrowed value is only valid for the block at 3:14! less_dangle.rs:3 if (true) {! less_dangle.rs:4 let temp = 23;! less_dangle.rs:5 dangler = &temp;! less_dangle.rs:6 } So dangler was borrowing the reference to temp and didn’t return it before temp expired. That’s not so much borrowing as stealing! And Rust will have no truck with that kind of behavior. This is one of the mechanisms Rust uses to keep track of all memory allocations and thus prevent Bad Things happening.
  36. 39.

    I’ll explain in the form of a code sample emulating

    a famous scene from the film Spartacus where Spartacus is trying to surrender to the romans, by declaring “I’m Spartacus”. One of his men then yells “No, I’m Spartacus”. And then they all start yelling it. It’s a fun thing to shout!
  37. 40.

    I’ll explain in the form of a code sample emulating

    a famous scene from the film Spartacus where Spartacus is trying to surrender to the romans, by declaring “I’m Spartacus”. One of his men then yells “No, I’m Spartacus”. And then they all start yelling it. It’s a fun thing to shout!
  38. 41.

    fn main() { let spartacus = ~"Spartacus"; println!("I'm {:?}!", spartacus);

    } Owned Pointers (~) So, here’s that squiggle again. Ok, a tilde if you insist! Rather than declaring Spartacus as a stack variable I’d like to allocate it on the Heap instead. That way it’s not going to disappear just because it’s lost scope. Also if I’d created it statically, it would just get stored as a series of unchangeable byte characters. ! On this occasion I’m formatting the output using the {:?} type. This displays the content as “debugger information” rather than casting it as something specific. It’s ugly and computationally expensive though, so no putting it in production code!
  39. 42.

    fn main() { let spartacus = ~"Spartacus"; println!("I'm {:?}!", spartacus);

    } Owned Pointers (~) So, here’s that squiggle again. Ok, a tilde if you insist! Rather than declaring Spartacus as a stack variable I’d like to allocate it on the Heap instead. That way it’s not going to disappear just because it’s lost scope. Also if I’d created it statically, it would just get stored as a series of unchangeable byte characters. ! On this occasion I’m formatting the output using the {:?} type. This displays the content as “debugger information” rather than casting it as something specific. It’s ugly and computationally expensive though, so no putting it in production code!
  40. 43.

    fn main() { let spartacus = ~"Spartacus"; println!("I'm {:?}!", spartacus);

    } I'm ~"Spartacus"! Owned Pointers (~) So, here’s that squiggle again. Ok, a tilde if you insist! Rather than declaring Spartacus as a stack variable I’d like to allocate it on the Heap instead. That way it’s not going to disappear just because it’s lost scope. Also if I’d created it statically, it would just get stored as a series of unchangeable byte characters. ! On this occasion I’m formatting the output using the {:?} type. This displays the content as “debugger information” rather than casting it as something specific. It’s ugly and computationally expensive though, so no putting it in production code!
  41. 44.

    fn main() { let spartacus = ~"Spartacus"; println!("I'm {:?}!", spartacus);

    ! let wonder_woman = spartacus; println!("No, I'm {:?}!!", wonder_woman); } Owned Pointers (~) Next up Wonder Woman steps in to take the rap. She takes ownership of the owned pointer by assigning herself to the spartacus variable. Only one variable is actually allowed to own an owned pointer, so ownership Moves to Wonder Woman moving it away from Spartacus. So now Wonder Woman gets to be Spartacus!
  42. 45.

    fn main() { let spartacus = ~"Spartacus"; println!("I'm {:?}!", spartacus);

    ! let wonder_woman = spartacus; println!("No, I'm {:?}!!", wonder_woman); } Owned Pointers (~) Next up Wonder Woman steps in to take the rap. She takes ownership of the owned pointer by assigning herself to the spartacus variable. Only one variable is actually allowed to own an owned pointer, so ownership Moves to Wonder Woman moving it away from Spartacus. So now Wonder Woman gets to be Spartacus!
  43. 46.

    fn main() { let spartacus = ~"Spartacus"; println!("I'm {:?}!", spartacus);

    ! let wonder_woman = spartacus; println!("No, I'm {:?}!!", wonder_woman); } I'm ~"Spartacus"! No, I'm ~"Spartacus"!! Owned Pointers (~) Next up Wonder Woman steps in to take the rap. She takes ownership of the owned pointer by assigning herself to the spartacus variable. Only one variable is actually allowed to own an owned pointer, so ownership Moves to Wonder Woman moving it away from Spartacus. So now Wonder Woman gets to be Spartacus!
  44. 47.

    fn main() { let spartacus = ~"Spartacus"; println!("I'm {:?}!", spartacus);

    ! let wonder_woman = spartacus; println!("No, I'm {:?}!!", wonder_woman); ! let hulk = wonder_woman; println!("NO! HULK IS {:?}!!!", hulk); } Owned Pointers (~) Similarly Hulk in turn can also take over ownership of the owned pointer by taking it from Wonder Woman. ! ‘NO! HULK IS ~”Spartacus”!!!’ ! Hulk really doesn’t get how this works…
  45. 48.

    fn main() { let spartacus = ~"Spartacus"; println!("I'm {:?}!", spartacus);

    ! let wonder_woman = spartacus; println!("No, I'm {:?}!!", wonder_woman); ! let hulk = wonder_woman; println!("NO! HULK IS {:?}!!!", hulk); } Owned Pointers (~) Similarly Hulk in turn can also take over ownership of the owned pointer by taking it from Wonder Woman. ! ‘NO! HULK IS ~”Spartacus”!!!’ ! Hulk really doesn’t get how this works…
  46. 49.

    fn main() { let spartacus = ~"Spartacus"; println!("I'm {:?}!", spartacus);

    ! let wonder_woman = spartacus; println!("No, I'm {:?}!!", wonder_woman); ! let hulk = wonder_woman; println!("NO! HULK IS {:?}!!!", hulk); } I'm ~"Spartacus"! No, I'm ~"Spartacus"!! NO! HULK IS ~"Spartacus"!!! Owned Pointers (~) Similarly Hulk in turn can also take over ownership of the owned pointer by taking it from Wonder Woman. ! ‘NO! HULK IS ~”Spartacus”!!!’ ! Hulk really doesn’t get how this works…
  47. 50.

    fn main() { let spartacus = ~"Spartacus"; println!("I'm {:s}!", spartacus);

    ! let wonder_woman = spartacus; println!("No, I'm {:s}!!", wonder_woman); ! let hulk = wonder_woman; println!("NO! HULK IS {:s}!!!", hulk); } I'm Spartacus!! No, I'm Spartacus!!! NO! HULK IS Spartacus!!! Owned Pointers (~) I think we’ve made it pretty clear that we’re outputting owned strings, so I’m going to switch out the output to plain strings. Much cleaner!
  48. 51.

    fn main() { let spartacus = ~"Spartacus"; println!("I'm {:s}!", spartacus);

    ! let wonder_woman = spartacus; println!("No, I'm {:s}!!", wonder_woman); ! let hulk = wonder_woman; println!("NO! HULK IS {:s}!!!", hulk); ! println!("No really, I am {:s} :(", spartacus); } Owned Pointers (~) Spartacus at this point tries to set everyone straight. But because ownership of ~”Spartacus” has moved, accessing the spartacus variable is doomed to compiler failure.
  49. 52.

    fn main() { let spartacus = ~"Spartacus"; println!("I'm {:s}!", spartacus);

    ! let wonder_woman = spartacus; println!("No, I'm {:s}!!", wonder_woman); ! let hulk = wonder_woman; println!("NO! HULK IS {:s}!!!", hulk); ! println!("No really, I am {:s} :(", spartacus); } spartacus.rs:11:39: 11:48 error: use of moved value: `spartacus` spartacus.rs:11 println!("No really, I am {:s}!", spartacus); ^~~~~~~~~ Owned Pointers (~) Spartacus at this point tries to set everyone straight. But because ownership of ~”Spartacus” has moved, accessing the spartacus variable is doomed to compiler failure.
  50. 53.

    fn main() { let spartacus = "Spartacus"; println!("I'm {:s}!", spartacus);

    ! let wonder_woman = spartacus; println!("No, I'm {:s}!!", wonder_woman); ! let hulk = wonder_woman; println!("NO! HULK IS {:s}!!!", hulk); ! println!("No really, I am {:s} :(", spartacus); } Owned Pointers (~) Of course we could just declare a normal string instead of an “owned” string. The we would be passing around a reference pointer which would not get moved. We would be subject to Borrowing restrictions rather than Moving restrictions. Of course they’re all wrong…
  51. 54.

    fn main() { let spartacus = "Spartacus"; println!("I'm {:s}!", spartacus);

    ! let wonder_woman = spartacus; println!("No, I'm {:s}!!", wonder_woman); ! let hulk = wonder_woman; println!("NO! HULK IS {:s}!!!", hulk); ! println!("No really, I am {:s} :(", spartacus); } I'm Spartacus! No, I'm Spartacus!! NO! HULK IS Spartacus!!! No really, I am Spartacus :( Owned Pointers (~) Of course we could just declare a normal string instead of an “owned” string. The we would be passing around a reference pointer which would not get moved. We would be subject to Borrowing restrictions rather than Moving restrictions. Of course they’re all wrong…
  52. 57.

    struct Liberator { name: ~str, favorite_color: &'static str } !

    fn defiance(prefix: &str, liberator: &mut Liberator) { liberator.name.push_char('!'); println!("{:s} {:s}", prefix, liberator.name); } ! fn main() { let mut spartacus = ~Liberator { name: ~"Spartacus", favorite_color: "chartreuse" }; defiance("I'm", spartacus); ! let mut wonder_woman = spartacus; defiance("No I'm", wonder_woman); ! let mut hulk = wonder_woman; defiance("NO! HULK IS", hulk); } Let’s throw something new into the mix: structs. I declare a liberator struct at the top here. I’ve decided the name is like before, an owned string, but for the favorite color I just decided to keep it static. Because I’m effectively borrowing a static value in a struct like this the compiler needs some kind of clue about how long lived it is, so I add the special lifetime type, ‘static, which lasts the length of the application. ! When I assign spartacus to an instance of Liberator I have to declare all the struct's properties (name and favorite_color). Yes, Spartacus’ favorite color is chartreuse. Not many people know that. ! I’ve also brought in a function, defiance() to handle all that glorious shouting. In the definition of defiance() we’re receiving the arguments as references. These parameters finish borrowing when the function has completed, so borrowing works pretty well here. I’m defined the liberator parameter as mutable so we can add extra exclamation marks to the name. ! End results: same results as before. This is just a refactor.
  53. 58.

    struct Liberator { name: ~str, favorite_color: &'static str } !

    fn defiance(prefix: &str, liberator: &mut Liberator) { liberator.name.push_char('!'); println!("{:s} {:s}", prefix, liberator.name); } ! fn main() { let mut spartacus = ~Liberator { name: ~"Spartacus", favorite_color: "chartreuse" }; defiance("I'm", spartacus); ! let mut wonder_woman = spartacus; defiance("No I'm", wonder_woman); ! let mut hulk = wonder_woman; defiance("NO! HULK IS", hulk); } Let’s throw something new into the mix: structs. I declare a liberator struct at the top here. I’ve decided the name is like before, an owned string, but for the favorite color I just decided to keep it static. Because I’m effectively borrowing a static value in a struct like this the compiler needs some kind of clue about how long lived it is, so I add the special lifetime type, ‘static, which lasts the length of the application. ! When I assign spartacus to an instance of Liberator I have to declare all the struct's properties (name and favorite_color). Yes, Spartacus’ favorite color is chartreuse. Not many people know that. ! I’ve also brought in a function, defiance() to handle all that glorious shouting. In the definition of defiance() we’re receiving the arguments as references. These parameters finish borrowing when the function has completed, so borrowing works pretty well here. I’m defined the liberator parameter as mutable so we can add extra exclamation marks to the name. ! End results: same results as before. This is just a refactor.
  54. 59.

    struct Liberator { name: ~str, favorite_color: &'static str } !

    fn defiance(prefix: &str, liberator: &mut Liberator) { liberator.name.push_char('!'); println!("{:s} {:s}", prefix, liberator.name); } ! fn main() { let mut spartacus = ~Liberator { name: ~"Spartacus", favorite_color: "chartreuse" }; defiance("I'm", spartacus); ! let mut wonder_woman = spartacus; defiance("No I'm", wonder_woman); ! let mut hulk = wonder_woman; defiance("NO! HULK IS", hulk); } Let’s throw something new into the mix: structs. I declare a liberator struct at the top here. I’ve decided the name is like before, an owned string, but for the favorite color I just decided to keep it static. Because I’m effectively borrowing a static value in a struct like this the compiler needs some kind of clue about how long lived it is, so I add the special lifetime type, ‘static, which lasts the length of the application. ! When I assign spartacus to an instance of Liberator I have to declare all the struct's properties (name and favorite_color). Yes, Spartacus’ favorite color is chartreuse. Not many people know that. ! I’ve also brought in a function, defiance() to handle all that glorious shouting. In the definition of defiance() we’re receiving the arguments as references. These parameters finish borrowing when the function has completed, so borrowing works pretty well here. I’m defined the liberator parameter as mutable so we can add extra exclamation marks to the name. ! End results: same results as before. This is just a refactor.
  55. 60.

    struct Liberator { name: ~str, favorite_color: &'static str } !

    fn defiance(prefix: &str, liberator: &mut Liberator) { liberator.name.push_char('!'); println!("{:s} {:s}", prefix, liberator.name); } ! fn main() { let mut spartacus = ~Liberator { name: ~"Spartacus", favorite_color: "chartreuse" }; defiance("I'm", spartacus); ! let mut wonder_woman = spartacus; defiance("No I'm", wonder_woman); ! let mut hulk = wonder_woman; defiance("NO! HULK IS", hulk); } Let’s throw something new into the mix: structs. I declare a liberator struct at the top here. I’ve decided the name is like before, an owned string, but for the favorite color I just decided to keep it static. Because I’m effectively borrowing a static value in a struct like this the compiler needs some kind of clue about how long lived it is, so I add the special lifetime type, ‘static, which lasts the length of the application. ! When I assign spartacus to an instance of Liberator I have to declare all the struct's properties (name and favorite_color). Yes, Spartacus’ favorite color is chartreuse. Not many people know that. ! I’ve also brought in a function, defiance() to handle all that glorious shouting. In the definition of defiance() we’re receiving the arguments as references. These parameters finish borrowing when the function has completed, so borrowing works pretty well here. I’m defined the liberator parameter as mutable so we can add extra exclamation marks to the name. ! End results: same results as before. This is just a refactor.
  56. 61.

    struct Liberator { name: ~str, favorite_color: &'static str } !

    fn defiance(prefix: &str, liberator: &mut Liberator) { liberator.name.push_char('!'); println!("{:s} {:s}", prefix, liberator.name); } ! fn main() { let mut spartacus = ~Liberator { name: ~"Spartacus", favorite_color: "chartreuse" }; defiance("I'm", spartacus); ! let mut wonder_woman = spartacus; defiance("No I'm", wonder_woman); ! let mut hulk = wonder_woman; defiance("NO! HULK IS", hulk); } Let’s throw something new into the mix: structs. I declare a liberator struct at the top here. I’ve decided the name is like before, an owned string, but for the favorite color I just decided to keep it static. Because I’m effectively borrowing a static value in a struct like this the compiler needs some kind of clue about how long lived it is, so I add the special lifetime type, ‘static, which lasts the length of the application. ! When I assign spartacus to an instance of Liberator I have to declare all the struct's properties (name and favorite_color). Yes, Spartacus’ favorite color is chartreuse. Not many people know that. ! I’ve also brought in a function, defiance() to handle all that glorious shouting. In the definition of defiance() we’re receiving the arguments as references. These parameters finish borrowing when the function has completed, so borrowing works pretty well here. I’m defined the liberator parameter as mutable so we can add extra exclamation marks to the name. ! End results: same results as before. This is just a refactor.
  57. 62.

    struct Liberator { name: ~str, favorite_color: &'static str } !

    fn defiance(prefix: &str, liberator: &mut Liberator) { liberator.name.push_char('!'); println!("{:s} {:s}", prefix, liberator.name); } ! fn main() { let mut spartacus = ~Liberator { name: ~"Spartacus", favorite_color: "chartreuse" }; defiance("I'm", spartacus); ! let mut wonder_woman = spartacus; defiance("No I'm", wonder_woman); ! let mut hulk = wonder_woman; defiance("NO! HULK IS", hulk); } I'm Spartacus! No I'm Spartacus!! NO! HULK IS Spartacus!!! Let’s throw something new into the mix: structs. I declare a liberator struct at the top here. I’ve decided the name is like before, an owned string, but for the favorite color I just decided to keep it static. Because I’m effectively borrowing a static value in a struct like this the compiler needs some kind of clue about how long lived it is, so I add the special lifetime type, ‘static, which lasts the length of the application. ! When I assign spartacus to an instance of Liberator I have to declare all the struct's properties (name and favorite_color). Yes, Spartacus’ favorite color is chartreuse. Not many people know that. ! I’ve also brought in a function, defiance() to handle all that glorious shouting. In the definition of defiance() we’re receiving the arguments as references. These parameters finish borrowing when the function has completed, so borrowing works pretty well here. I’m defined the liberator parameter as mutable so we can add extra exclamation marks to the name. ! End results: same results as before. This is just a refactor.
  58. 63.

    struct Liberator { name: ~str, favorite_color: &'static str } !

    impl Liberator { fn defiance(&mut self, prefix: &str) { self.name.push_char('!'); println!("{:s} {:s}", prefix, self.name); } } ! fn main() { let mut spartacus = ~Liberator { name: ~"Spartacus", favorite_color: "chartreuse" }; spartacus.defiance("I'm"); ! let mut wonder_woman = spartacus; wonder_woman.defiance("No I'm"); ! let mut hulk = wonder_woman; hulk.defiance("NO! HULK IS"); } I'm Spartacus!! No I'm Spartacus!!! NO! HULK IS Spartacus!!! Defiance really feels like it should be a method of Liberator. We can do that by declaring an impl block for Liberator. ! When calling defiance we just pass one argument now, covering the preamble (“I’m”, “No I’m”, “NO! HULK IS”, etc). ! When we define the function we have to declare an extra parameter called ‘self’. This is used to self reference inside the function. Kind of similar to how python declares methods. One difference with python though, you add modifiers like &, ~ and mut depending on how self is being accessed. In this we going with &mut so we can can change the name property.
  59. 64.

    struct Liberator { name: ~str, favorite_color: &'static str } !

    impl Liberator { fn defiance(&mut self, prefix: &str) { self.name.push_char('!'); println!("{:s} {:s}", prefix, self.name); } } ! fn main() { let mut spartacus = ~Liberator { name: ~"Spartacus", favorite_color: "chartreuse" }; spartacus.defiance("I'm"); ! let mut wonder_woman = spartacus; wonder_woman.defiance("No I'm"); ! let mut hulk = wonder_woman; hulk.defiance("NO! HULK IS"); } I'm Spartacus!! No I'm Spartacus!!! NO! HULK IS Spartacus!!! Defiance really feels like it should be a method of Liberator. We can do that by declaring an impl block for Liberator. ! When calling defiance we just pass one argument now, covering the preamble (“I’m”, “No I’m”, “NO! HULK IS”, etc). ! When we define the function we have to declare an extra parameter called ‘self’. This is used to self reference inside the function. Kind of similar to how python declares methods. One difference with python though, you add modifiers like &, ~ and mut depending on how self is being accessed. In this we going with &mut so we can can change the name property.
  60. 65.

    struct Liberator { name: ~str, favorite_color: &'static str } !

    impl Liberator { fn defiance(&mut self, prefix: &str) { self.name.push_char('!'); println!("{:s} {:s}", prefix, self.name); } } ! fn main() { let mut spartacus = ~Liberator { name: ~"Spartacus", favorite_color: "chartreuse" }; spartacus.defiance("I'm"); ! let mut wonder_woman = spartacus; wonder_woman.defiance("No I'm"); ! let mut hulk = wonder_woman; hulk.defiance("NO! HULK IS"); } I'm Spartacus!! No I'm Spartacus!!! NO! HULK IS Spartacus!!! Defiance really feels like it should be a method of Liberator. We can do that by declaring an impl block for Liberator. ! When calling defiance we just pass one argument now, covering the preamble (“I’m”, “No I’m”, “NO! HULK IS”, etc). ! When we define the function we have to declare an extra parameter called ‘self’. This is used to self reference inside the function. Kind of similar to how python declares methods. One difference with python though, you add modifiers like &, ~ and mut depending on how self is being accessed. In this we going with &mut so we can can change the name property.
  61. 66.

    So… how about we do something useful with shared pointers?

    How about Linked Lists? For a pacman game…
  62. 67.

    enum List{ Node(~str, ~List), Nada } ! fn main() {

    let blinky = ~Node(~"Blinky", ~Nada); let ghosts = blinky; } Building a Linked List We start off with a list of 1 item, the ghost Blinky. Blinky is defined using the Node tuple for defining a ghost name, and linking to the next link item.
  63. 68.

    enum List{ Node(~str, ~List), Nada } ! fn main() {

    let blinky = ~Node(~"Blinky", ~Nada); let ghosts = blinky; } Building a Linked List Our list is terminated with a List token we created named “Nada”. There is no concept of null in Rust. Nulls lead to bugs so Rust stays clear of them. ! Notice how Node and Nada are stored in a List enum? Enums in Rust are not limited to tokens or tokens with integer values. Our List enum used for each ghost gives us a choice between a Node tuple or a Nada terminator token.
  64. 69.

    enum List{ Node(~str, ~List), Nada } ! impl List {

    fn announce(~self) { match *self { Node(name, next) => { println!("{:s} has entered the game!", name); next.announce(); }, Nada => () } } } ! fn main() { let blinky = ~Node(~"Blinky", ~Nada); let ghosts = blinky; ! ghosts.announce(); } Building a Linked List Might be nice if we did something with this linked list, so lets implement a method called announce() that shows which ghosts have entered the game. Yes, use of ‘impl' is not limited to just structs! ! Our announce method is going to be called recursively. Borrowing isn’t going to be practical for this example, so we’ll define self as an owned pointer. ! We need to access values in our List enum. For that we’ll use ‘match’ which is a bit like a case statement. When matching value ‘self’ points to (dereferenced using ‘*’ before self), we need entries that cover all enum variants.
  65. 70.

    enum List{ Node(~str, ~List), Nada } ! impl List {

    fn announce(~self) { match *self { Node(name, next) => { println!("{:s} has entered the game!", name); next.announce(); }, Nada => () } } } ! fn main() { let blinky = ~Node(~"Blinky", ~Nada); let ghosts = blinky; ! ghosts.announce(); } Blinky has entered the game! Building a Linked List Might be nice if we did something with this linked list, so lets implement a method called announce() that shows which ghosts have entered the game. Yes, use of ‘impl' is not limited to just structs! ! Our announce method is going to be called recursively. Borrowing isn’t going to be practical for this example, so we’ll define self as an owned pointer. ! We need to access values in our List enum. For that we’ll use ‘match’ which is a bit like a case statement. When matching value ‘self’ points to (dereferenced using ‘*’ before self), we need entries that cover all enum variants.
  66. 71.

    enum List{ Node(~str, ~List), Nada } ! impl List {

    fn announce(~self) { match *self { Node(name, next) => { println!("{:s} has entered the game!", name); next.announce(); }, Nada => () } } } ! fn main() { let blinky = ~Node(~"Blinky", ~Nada); let pinky = ~Node(~"Pinky", blinky); let ghosts = pinky; ! ghosts.announce(); } We can now Pinky to the linked list.
  67. 72.

    enum List{ Node(~str, ~List), Nada } ! impl List {

    fn announce(~self) { match *self { Node(name, next) => { println!("{:s} has entered the game!", name); next.announce(); }, Nada => () } } } ! fn main() { let blinky = ~Node(~"Blinky", ~Nada); let pinky = ~Node(~"Pinky", blinky); let ghosts = pinky; ! ghosts.announce(); } We’ve tethered Pinky to Blinky like so…
  68. 73.

    enum List{ Node(~str, ~List), Nada } ! impl List {

    fn announce(~self) { match *self { Node(name, next) => { println!("{:s} has entered the game!", name); next.announce(); }, Nada => () } } } ! fn main() { let blinky = ~Node(~"Blinky", ~Nada); let pinky = ~Node(~"Pinky", blinky); let ghosts = pinky; ! ghosts.announce(); } We’ve tethered Pinky to Blinky like so…
  69. 74.

    enum List{ Node(~str, ~List), Nada } ! impl List {

    fn announce(~self) { match *self { Node(name, next) => { println!("{:s} has entered the game!", name); next.announce(); }, Nada => () } } } ! fn main() { let blinky = ~Node(~"Blinky", ~Nada); let pinky = ~Node(~"Pinky", blinky); let ghosts = pinky; ! ghosts.announce(); } Pinky has entered the game! Blinky has entered the game! We’ve tethered Pinky to Blinky like so…
  70. 75.

    enum List{ Node(~str, ~List), Nada } ! impl List {

    fn announce(~self) { match *self { Node(name, next) => { println!("{:s} has entered the game!", name); next.announce(); }, Nada => () } } } ! fn main() { let blinky = ~Node(~"Blinky", ~Nada); let pinky = ~Node(~"Pinky", blinky); let inky = ~Node(~"Inky", pinky); let clyde = ~Node(~"Clyde", inky); let ghosts = clyde; ! ghosts.announce(); } And so on…
  71. 76.

    enum List{ Node(~str, ~List), Nada } ! impl List {

    fn announce(~self) { match *self { Node(name, next) => { println!("{:s} has entered the game!", name); next.announce(); }, Nada => () } } } ! fn main() { let blinky = ~Node(~"Blinky", ~Nada); let pinky = ~Node(~"Pinky", blinky); let inky = ~Node(~"Inky", pinky); let clyde = ~Node(~"Clyde", inky); let ghosts = clyde; ! ghosts.announce(); } And so on…
  72. 77.

    enum List{ Node(~str, ~List), Nada } ! impl List {

    fn announce(~self) { match *self { Node(name, next) => { println!("{:s} has entered the game!", name); next.announce(); }, Nada => () } } } ! fn main() { let blinky = ~Node(~"Blinky", ~Nada); let pinky = ~Node(~"Pinky", blinky); let inky = ~Node(~"Inky", pinky); let clyde = ~Node(~"Clyde", inky); let ghosts = clyde; ! ghosts.announce(); } Clyde has entered the game! Inky has entered the game! Pinky has entered the game! Blinky has entered the game! And so on…
  73. 79.

    struct Wotsit { item: int, description: ~str } ! impl

    Wotsit { fn examine(&self) { println!("This wotsit is brought to you by the number {:d}", self.item); } } ! fn main() { let the_answer = Wotsit { item: 42, description: ~"The ultimate answer"}; the_answer.examine(); } Generics and Traits Starting off, I’ve got a wotsit that can hold integer values. But I’d really like it to be able to contain anything.
  74. 80.

    struct Wotsit { item: int, description: ~str } ! impl

    Wotsit { fn examine(&self) { println!("This wotsit is brought to you by the number {:d}", self.item); } } ! fn main() { let the_answer = Wotsit { item: 42, description: ~"The ultimate answer"}; the_answer.examine(); } This wotsit is brought to you by the number 42 Generics and Traits Starting off, I’ve got a wotsit that can hold integer values. But I’d really like it to be able to contain anything.
  75. 81.

    struct Wotsit<T> { item: T, description: ~str } ! impl

    Wotsit { fn examine(&self) { println!("This wotsit is brought to you by the number {:d}", self.item); } } ! fn main() { let the_answer = Wotsit { item: 42, description: ~"The ultimate answer"}; the_answer.examine(); } Generics and Traits So I’ve tweaked the struct a little so it accepts any type. A consequence of this Wotsit implementation is no longer good. Its designed to work for integers only
  76. 82.

    struct Wotsit<T> { item: T, description: ~str } ! trait

    Viewable{ fn examine(&self) { println!("This wotsit contains... you know... thingies"); } } ! impl Viewable for Wotsit<int> {} ! fn main() { let the_answer = Wotsit { item: 42, description: ~"The ultimate answer"}; the_answer.examine(); } Generics and Traits So to deal with these type specific implementations I’ve implemented a trait called Viewable. Viewable is defined now for integer Wotsits. I’ve given Viewable a default for the examine function that prints this really vague piece of information…
  77. 83.

    struct Wotsit<T> { item: T, description: ~str } ! trait

    Viewable{ fn examine(&self) { println!("This wotsit contains... you know... thingies"); } } ! impl Viewable for Wotsit<int> {} ! fn main() { let the_answer = Wotsit { item: 42, description: ~"The ultimate answer"}; the_answer.examine(); } Generics and Traits So to deal with these type specific implementations I’ve implemented a trait called Viewable. Viewable is defined now for integer Wotsits. I’ve given Viewable a default for the examine function that prints this really vague piece of information…
  78. 84.

    struct Wotsit<T> { item: T, description: ~str } ! trait

    Viewable{ fn examine(&self) { println!("This wotsit contains... you know... thingies"); } } ! impl Viewable for Wotsit<int> {} ! fn main() { let the_answer = Wotsit { item: 42, description: ~"The ultimate answer"}; the_answer.examine(); } This wotsit contains... you know... thingies Generics and Traits So to deal with these type specific implementations I’ve implemented a trait called Viewable. Viewable is defined now for integer Wotsits. I’ve given Viewable a default for the examine function that prints this really vague piece of information…
  79. 85.

    struct Wotsit<T> { item: T, description: ~str } ! trait

    Viewable{ fn examine(&self) { println!("This wotsit contains... you know... thingies"); } } ! impl Viewable for Wotsit<int> { fn examine(&self) { println!("This wotsit is brought to you by the number {:d}", self.item); } } ! fn main() { let the_answer = Wotsit { item: 42, description: ~"The ultimate answer"}; the_answer.examine(); } That’s a bit too vague, so we’ll just override the examine method for integer Wotsits.
  80. 86.

    struct Wotsit<T> { item: T, description: ~str } ! trait

    Viewable{ fn examine(&self) { println!("This wotsit contains... you know... thingies"); } } ! impl Viewable for Wotsit<int> { fn examine(&self) { println!("This wotsit is brought to you by the number {:d}", self.item); } } ! fn main() { let the_answer = Wotsit { item: 42, description: ~"The ultimate answer"}; the_answer.examine(); } This wotsit is brought to you by the number 42 That’s a bit too vague, so we’ll just override the examine method for integer Wotsits.
  81. 87.

    struct Wotsit<T> { item: T, description: ~str } ! trait

    Viewable{ fn examine(&self) { println!("This wotsit contains... you know... thingies"); } } ! impl Viewable for Wotsit<int> { fn examine(&self) { println!("This wotsit is brought to you by the number {:d}", self.item); } } ! impl Viewable for Wotsit<~str> { fn examine(&self) { println!("This wotsit contains a '{:s}'", self.item); } } ! fn main() { let the_answer = Wotsit { item: 42, description: ~"The ultimate answer"}; the_answer.examine(); let power_cell = Wotsit { item: ~"confused cat", description: ~"Powers the internet"}; power_cell.examine(); } … and lets expand it’s capabilities to actor for owned strings….
  82. 88.

    struct Wotsit<T> { item: T, description: ~str } ! trait

    Viewable{ fn examine(&self) { println!("This wotsit contains... you know... thingies"); } } ! impl Viewable for Wotsit<int> { fn examine(&self) { println!("This wotsit is brought to you by the number {:d}", self.item); } } ! impl Viewable for Wotsit<~str> { fn examine(&self) { println!("This wotsit contains a '{:s}'", self.item); } } ! fn main() { let the_answer = Wotsit { item: 42, description: ~"The ultimate answer"}; the_answer.examine(); let power_cell = Wotsit { item: ~"confused cat", description: ~"Powers the internet"}; power_cell.examine(); } … and lets expand it’s capabilities to actor for owned strings….
  83. 89.

    struct Wotsit<T> { item: T, description: ~str } ! trait

    Viewable{ fn examine(&self) { println!("This wotsit contains... you know... thingies"); } } ! impl Viewable for Wotsit<int> { fn examine(&self) { println!("This wotsit is brought to you by the number {:d}", self.item); } } ! impl Viewable for Wotsit<~str> { fn examine(&self) { println!("This wotsit contains a '{:s}'", self.item); } } ! fn main() { let the_answer = Wotsit { item: 42, description: ~"The ultimate answer"}; the_answer.examine(); let power_cell = Wotsit { item: ~"confused cat", description: ~"Powers the internet"}; power_cell.examine(); } … and lets expand it’s capabilities to actor for owned strings….
  84. 90.

    struct Wotsit<T> { item: T, description: ~str } ! trait

    Viewable{ fn examine(&self) { println!("This wotsit contains... you know... thingies"); } } ! impl Viewable for Wotsit<int> { fn examine(&self) { println!("This wotsit is brought to you by the number {:d}", self.item); } } ! impl Viewable for Wotsit<~str> { fn examine(&self) { println!("This wotsit contains a '{:s}'", self.item); } } ! fn main() { let the_answer = Wotsit { item: 42, description: ~"The ultimate answer"}; the_answer.examine(); let power_cell = Wotsit { item: ~"confused cat", description: ~"Powers the internet"}; power_cell.examine(); } This wotsit is brought to you by the number 42 This wotsit contains a 'confused cat' … and lets expand it’s capabilities to actor for owned strings….
  85. 92.

    fn(x) { ! } counter += counter + x; 1)

    For those of you need reminding, a closure is typically an anonymous function that 2) can influence variable outside of it’s internal scope. So that each time you call it can change state. So in this example the closure is updating the outer counter variable each time it gets run. 3) Although of course closures in Rust look more like ruby blocks.
  86. 93.

    fn(x) { ! } Function Scope Outer let mut counter

    = 4; counter += counter + x; 1) For those of you need reminding, a closure is typically an anonymous function that 2) can influence variable outside of it’s internal scope. So that each time you call it can change state. So in this example the closure is updating the outer counter variable each time it gets run. 3) Although of course closures in Rust look more like ruby blocks.
  87. 94.

    Function Scope Outer let mut counter = 4; counter +=

    counter + x; let closure = |x| { ! } ! ! 1) For those of you need reminding, a closure is typically an anonymous function that 2) can influence variable outside of it’s internal scope. So that each time you call it can change state. So in this example the closure is updating the outer counter variable each time it gets run. 3) Although of course closures in Rust look more like ruby blocks.
  88. 95.

    use std::io::timer::sleep; ! // Rust 0.10 fn main() { let

    actions = [("Captain America", "bashes", 20), ("Black Widow", "slashes", 25), ("Ironman", "throws cash at", 0), ("Hulk", "SMASHES", 200)]; ! let mut outcomes = actions.iter() .map(|&action| { let (hero, attack, damage) = action; format!("{:s} {:s} Red Skull for {:d} damage", hero, attack, damage) }); ! for outcome in outcomes { spawn(proc() { sleep(500); println!(“{:s}”, outcome); }); } } Closures As you may remember we used closures earlier in the map function for building outcome strings. And also for spawning tasks.
  89. 96.

    use std::io::timer::sleep; ! // Rust 0.10 fn main() { let

    actions = [("Captain America", "bashes", 20), ("Black Widow", "slashes", 25), ("Ironman", "throws cash at", 0), ("Hulk", "SMASHES", 200)]; ! let mut outcomes = actions.iter() .map(|&action| { let (hero, attack, damage) = action; format!("{:s} {:s} Red Skull for {:d} damage", hero, attack, damage) }); ! for outcome in outcomes { spawn(proc() { sleep(500); println!(“{:s}”, outcome); }); } } Closures As you may remember we used closures earlier in the map function for building outcome strings. And also for spawning tasks.
  90. 97.

    fn main() { let mut outer_value = 0; ! let

    increment = |x| { outer_value += x; }; ! increment(7); println!("outer_value now equals {:d}", outer_value); } Closures Let’s play! To start off with we have a simple “increment” closure. We execute it with an argument of 7. Inside the closure x will equal 7, which gets added onto the outer_value.
  91. 98.

    fn main() { let mut outer_value = 0; ! let

    increment = |x| { outer_value += x; }; ! increment(7); println!("outer_value now equals {:d}", outer_value); } outer_value now equals 7 Closures Let’s play! To start off with we have a simple “increment” closure. We execute it with an argument of 7. Inside the closure x will equal 7, which gets added onto the outer_value.
  92. 99.

    fn transform(mutator: |int| -> int, value: int ) { let

    result = mutator(value); println!("outer_value now equals {:d}", result); } ! fn main() { let mut outer_value = 0; ! let increment = |x| { outer_value += x; outer_value }; ! transform(increment, 7); } outer_value now equals 7 We can pass this closure into a function as an argument. Transform will accept any closure along with an input value. So again we get 7.
  93. 100.

    fn transform(mutator: |int| -> int, value: int ) { let

    result = mutator(value); println!("outer_value now equals {:d}", result); } ! fn main() { let mut outer_value = 0; ! let increment = |x| { outer_value += x; outer_value }; ! transform(increment, 7); transform(increment, 35); } As I mentioned earlier, we expect closures to continually change state. Lets test that by transforming increment twice. This doesn’t go so well. The problem is we passed the closure as what’s known as a stack closure. A closure defined on the stack. When it got passed in the ownership moved making it unsafe to call twice. The compiler has actually told us how to work around this though. We just have to wrap the stack closure in another closure as we call it.
  94. 101.

    fn transform(mutator: |int| -> int, value: int ) { let

    result = mutator(value); println!("outer_value now equals {:d}", result); } ! fn main() { let mut outer_value = 0; ! let increment = |x| { outer_value += x; outer_value }; ! transform(increment, 7); transform(increment, 35); } closures.rs:15:15: 15:24 error: use of moved value: `increment` closures.rs:15 transform(increment, 35); ^~~~~~~~~ closures.rs:14:15: 14:24 note: `increment` moved here because it has type `|int| -> int`, which is a non-copyable stack closure (capture it in a new closure, e.g. `|x| f(x)`, to override) closures.rs:14 transform(increment, 7); ^~~~~~~~~ As I mentioned earlier, we expect closures to continually change state. Lets test that by transforming increment twice. This doesn’t go so well. The problem is we passed the closure as what’s known as a stack closure. A closure defined on the stack. When it got passed in the ownership moved making it unsafe to call twice. The compiler has actually told us how to work around this though. We just have to wrap the stack closure in another closure as we call it.
  95. 102.

    fn transform(mutator: |int| -> int, value: int ) { let

    result = mutator(value); println!("outer_value now equals {:d}", result); } ! fn main() { let mut outer_value = 0; ! let increment = |x| { outer_value += x; outer_value }; ! transform(|x| { increment(x) }, 7); transform(|x| { increment(x) }, 35); } So now we wrap increment in a closure as we call transform and all is good!
  96. 103.

    fn transform(mutator: |int| -> int, value: int ) { let

    result = mutator(value); println!("outer_value now equals {:d}", result); } ! fn main() { let mut outer_value = 0; ! let increment = |x| { outer_value += x; outer_value }; ! transform(|x| { increment(x) }, 7); transform(|x| { increment(x) }, 35); } outer_value now equals 7 outer_value now equals 42 So now we wrap increment in a closure as we call transform and all is good!
  97. 104.

    fn transform(mutator: |int| -> int, value: int ) { let

    result = mutator(value); println!(“ outer_value now equals {:d}", result); } ! fn main() { let mut outer_inc_value = 0; let increment = |x| { outer_inc_value += x; outer_inc_value }; ! let mut outer_mul_value = 1; let multiply = |x| { outer_mul_value *= x; outer_mul_value }; ! println!(“Additions:"); transform(|x| { increment(x) }, 7); transform(|x| { increment(x) }, 35); ! println!(“Multiplications:"); transform(|x| { multiply(x) }, 5); transform(|x| { multiply(x) }, 3); } I mentioned earlier that transform can take any closure as an input, as long as it contains a single argument. So lets try a multiplication closure…
  98. 105.

    fn transform(mutator: |int| -> int, value: int ) { let

    result = mutator(value); println!(“ outer_value now equals {:d}", result); } ! fn main() { let mut outer_inc_value = 0; let increment = |x| { outer_inc_value += x; outer_inc_value }; ! let mut outer_mul_value = 1; let multiply = |x| { outer_mul_value *= x; outer_mul_value }; ! println!(“Additions:"); transform(|x| { increment(x) }, 7); transform(|x| { increment(x) }, 35); ! println!(“Multiplications:"); transform(|x| { multiply(x) }, 5); transform(|x| { multiply(x) }, 3); } Additions: outer_value now equals 7 outer_value now equals 42 Multiplications: outer_value now equals 5 outer_value now equals 15 I mentioned earlier that transform can take any closure as an input, as long as it contains a single argument. So lets try a multiplication closure…
  99. 108.

    Tasks Rust use the CSP model of concurrency. Which stands

    for Communicating Sequential Processes. Using this model rust threads (know as Tasks) can communicate through channels or pipes. When creating the simplest variant, a “Sender” variable is provided for sending information and a “Port” variable is provided for receiving. So now Task A can send a message to Task B asynchronously despite being on a different thread. Task B listens by calling the recv() method. Task A sends data using the send() method. The companion cube has just sent a little heart to the cake. ! This is just the simplest type of channel. For example duplex channels are also available in Rust for sending both ways.
  100. 109.

    Tasks CSP - Communicating Sequential Processes Rust use the CSP

    model of concurrency. Which stands for Communicating Sequential Processes. Using this model rust threads (know as Tasks) can communicate through channels or pipes. When creating the simplest variant, a “Sender” variable is provided for sending information and a “Port” variable is provided for receiving. So now Task A can send a message to Task B asynchronously despite being on a different thread. Task B listens by calling the recv() method. Task A sends data using the send() method. The companion cube has just sent a little heart to the cake. ! This is just the simplest type of channel. For example duplex channels are also available in Rust for sending both ways.
  101. 110.

    Tasks CSP - Communicating Sequential Processes Rust use the CSP

    model of concurrency. Which stands for Communicating Sequential Processes. Using this model rust threads (know as Tasks) can communicate through channels or pipes. When creating the simplest variant, a “Sender” variable is provided for sending information and a “Port” variable is provided for receiving. So now Task A can send a message to Task B asynchronously despite being on a different thread. Task B listens by calling the recv() method. Task A sends data using the send() method. The companion cube has just sent a little heart to the cake. ! This is just the simplest type of channel. For example duplex channels are also available in Rust for sending both ways.
  102. 111.

    Tasks CSP - Communicating Sequential Processes Sender<~str> Task A Rust

    use the CSP model of concurrency. Which stands for Communicating Sequential Processes. Using this model rust threads (know as Tasks) can communicate through channels or pipes. When creating the simplest variant, a “Sender” variable is provided for sending information and a “Port” variable is provided for receiving. So now Task A can send a message to Task B asynchronously despite being on a different thread. Task B listens by calling the recv() method. Task A sends data using the send() method. The companion cube has just sent a little heart to the cake. ! This is just the simplest type of channel. For example duplex channels are also available in Rust for sending both ways.
  103. 112.

    Tasks CSP - Communicating Sequential Processes Sender<~str> Task A Receiver<~str>

    Task B Rust use the CSP model of concurrency. Which stands for Communicating Sequential Processes. Using this model rust threads (know as Tasks) can communicate through channels or pipes. When creating the simplest variant, a “Sender” variable is provided for sending information and a “Port” variable is provided for receiving. So now Task A can send a message to Task B asynchronously despite being on a different thread. Task B listens by calling the recv() method. Task A sends data using the send() method. The companion cube has just sent a little heart to the cake. ! This is just the simplest type of channel. For example duplex channels are also available in Rust for sending both ways.
  104. 113.

    Tasks println(receiver.recv()); CSP - Communicating Sequential Processes Sender<~str> Task A

    Receiver<~str> Task B Rust use the CSP model of concurrency. Which stands for Communicating Sequential Processes. Using this model rust threads (know as Tasks) can communicate through channels or pipes. When creating the simplest variant, a “Sender” variable is provided for sending information and a “Port” variable is provided for receiving. So now Task A can send a message to Task B asynchronously despite being on a different thread. Task B listens by calling the recv() method. Task A sends data using the send() method. The companion cube has just sent a little heart to the cake. ! This is just the simplest type of channel. For example duplex channels are also available in Rust for sending both ways.
  105. 114.

    Tasks sender.send(“<3!”); println(receiver.recv()); CSP - Communicating Sequential Processes Sender<~str> Task

    A Receiver<~str> Task B Rust use the CSP model of concurrency. Which stands for Communicating Sequential Processes. Using this model rust threads (know as Tasks) can communicate through channels or pipes. When creating the simplest variant, a “Sender” variable is provided for sending information and a “Port” variable is provided for receiving. So now Task A can send a message to Task B asynchronously despite being on a different thread. Task B listens by calling the recv() method. Task A sends data using the send() method. The companion cube has just sent a little heart to the cake. ! This is just the simplest type of channel. For example duplex channels are also available in Rust for sending both ways.
  106. 115.

    Tasks sender.send(“<3!”); println(receiver.recv()); <3! CSP - Communicating Sequential Processes Sender<~str>

    Task A Receiver<~str> Task B Rust use the CSP model of concurrency. Which stands for Communicating Sequential Processes. Using this model rust threads (know as Tasks) can communicate through channels or pipes. When creating the simplest variant, a “Sender” variable is provided for sending information and a “Port” variable is provided for receiving. So now Task A can send a message to Task B asynchronously despite being on a different thread. Task B listens by calling the recv() method. Task A sends data using the send() method. The companion cube has just sent a little heart to the cake. ! This is just the simplest type of channel. For example duplex channels are also available in Rust for sending both ways.
  107. 116.

    fn main() { let (sender, receiver) = channel(); ! spawn(proc()

    { sender.send("Marco!"); sender.send("Pollooo!"); }); ! for _ in range(0, 2) { println!("Got back '{}' from a rust task!”, receiver.recv()); } } Tasks So here’s something similar as actual code. In this case we just have a single task created through the spawn function. It contains a proc, an owned closure. Because it’s a closure it can still access the chan variable despite it being in the other scope. In this case we’re just receiving the messages from the outer function itself. Note that in the for loop I didn’t actually need the variable containing the range number, so I just put in an underscore as a placeholder so we don’t get unused local variable warnings. Let’s add more tasks.
  108. 117.

    fn main() { let (sender, receiver) = channel(); ! spawn(proc()

    { sender.send("Marco!"); sender.send("Pollooo!"); }); ! for _ in range(0, 2) { println!("Got back '{}' from a rust task!”, receiver.recv()); } } Got back 'Marco!' from a rust task! Got back 'Pollooo!' from a rust task! Tasks So here’s something similar as actual code. In this case we just have a single task created through the spawn function. It contains a proc, an owned closure. Because it’s a closure it can still access the chan variable despite it being in the other scope. In this case we’re just receiving the messages from the outer function itself. Note that in the for loop I didn’t actually need the variable containing the range number, so I just put in an underscore as a placeholder so we don’t get unused local variable warnings. Let’s add more tasks.
  109. 118.

    use std::ascii::StrAsciiExt; ! fn main() { let (sender1, receiver1) =

    channel(); let (sender2, receiver2) = channel(); ! sender1.send(~"ycnerrucnoc hcus"); sender1.send(~"wow secar ynam"); sender1.send(~""); ! spawn(proc() { loop { let code = receiver1.recv(); sender2.send(code.to_ascii_upper()); if code == ~"" { break; } } }); ! spawn(proc() { loop { let code = receiver2.recv(); if code == ~"" { break; } else { println!("Got back '{:s}'", code); } } }); } In this sample we’re communicating directly between to tasks using 2 different channels. We use the first channel to send 2 encrypted messages to the first tasks which converts it to upper case and then relays it to a second task to output the results. For this setup you can keep sending messages until a blank message is received at which point the listeners quit. I’ve implemented the repeat until using loops and breaks. Most of the code inside the first task is just reading and writing to the channel. We could refactor that into another function.
  110. 119.

    use std::ascii::StrAsciiExt; ! fn main() { let (sender1, receiver1) =

    channel(); let (sender2, receiver2) = channel(); ! sender1.send(~"ycnerrucnoc hcus"); sender1.send(~"wow secar ynam"); sender1.send(~""); ! spawn(proc() { loop { let code = receiver1.recv(); sender2.send(code.to_ascii_upper()); if code == ~"" { break; } } }); ! spawn(proc() { loop { let code = receiver2.recv(); if code == ~"" { break; } else { println!("Got back '{:s}'", code); } } }); } Got back 'YCNERRUCNOC HCUS' Got back 'WOW SECAR YNAM' In this sample we’re communicating directly between to tasks using 2 different channels. We use the first channel to send 2 encrypted messages to the first tasks which converts it to upper case and then relays it to a second task to output the results. For this setup you can keep sending messages until a blank message is received at which point the listeners quit. I’ve implemented the repeat until using loops and breaks. Most of the code inside the first task is just reading and writing to the channel. We could refactor that into another function.
  111. 120.

    fn main() { ... ! spawn(proc() { loop { let

    code = receiver1.recv(); sender2.send(code.to_ascii_upper()); if code == ~""{ break; } } }); ! spawn(proc() { loop { let code = receiver2.recv(); let s: ~str = code.chars_rev().collect(); sender3.send(s); if code == ~""{ break; } } }); ! spawn(proc() { loop { let code = receiver3.recv(); if code == ~""{ break; } else { println!("Got back ‘{:s}’", code); } } }); ! ... } One refactoring later, I’ve got a function called transform for manage the channels. Just tell tell transform which port you’re listening on, which channel you’re sending on and a closure for doing the actual transformation. In this case the closure just contains instructions to convert the strings to upper case. This will make it really easy to add a 3rd task.
  112. 121.

    fn main() { ... ! spawn(proc() { loop { let

    code = receiver1.recv(); sender2.send(code.to_ascii_upper()); if code == ~""{ break; } } }); ! spawn(proc() { loop { let code = receiver2.recv(); let s: ~str = code.chars_rev().collect(); sender3.send(s); if code == ~""{ break; } } }); ! spawn(proc() { loop { let code = receiver3.recv(); if code == ~""{ break; } else { println!("Got back ‘{:s}’", code); } } }); ! ... } One refactoring later, I’ve got a function called transform for manage the channels. Just tell tell transform which port you’re listening on, which channel you’re sending on and a closure for doing the actual transformation. In this case the closure just contains instructions to convert the strings to upper case. This will make it really easy to add a 3rd task.
  113. 122.

    fn main() { ... ! spawn(proc() { loop { let

    code = receiver1.recv(); sender2.send(code.to_ascii_upper()); if code == ~""{ break; } } }); ! spawn(proc() { loop { let code = receiver2.recv(); let s: ~str = code.chars_rev().collect(); sender3.send(s); if code == ~""{ break; } } }); ! spawn(proc() { loop { let code = receiver3.recv(); if code == ~""{ break; } else { println!("Got back ‘{:s}’", code); } } }); ! ... } One refactoring later, I’ve got a function called transform for manage the channels. Just tell tell transform which port you’re listening on, which channel you’re sending on and a closure for doing the actual transformation. In this case the closure just contains instructions to convert the strings to upper case. This will make it really easy to add a 3rd task.
  114. 123.

    fn transform(receiver: Receiver<~str>, sender: Sender<~str>, transformation: |~str| -> ~str) {

    loop { let code = receiver.recv(); if code == ~"" { sender.send(~""); break; } sender.send( transformation(code) ); } } ! fn main() { ... ! spawn(proc() { transform(receiver1, sender2, |x: ~str| x.to_ascii_upper()); }); ! spawn(proc() { transform(receiver2, sender3, |x: ~str| x.chars_rev().collect()); }); ! spawn(proc() { loop { let code = receiver3.recv(); if code == ~"" { break; } else { println!("Got back '{:s}'", code); } } }); } So now I’m adding a 3rd task and a 3rd channel to reverse the order of the text characters. Giving us…
  115. 124.

    fn transform(receiver: Receiver<~str>, sender: Sender<~str>, transformation: |~str| -> ~str) {

    loop { let code = receiver.recv(); if code == ~"" { sender.send(~""); break; } sender.send( transformation(code) ); } } ! fn main() { ... ! spawn(proc() { transform(receiver1, sender2, |x: ~str| x.to_ascii_upper()); }); ! spawn(proc() { transform(receiver2, sender3, |x: ~str| x.chars_rev().collect()); }); ! spawn(proc() { loop { let code = receiver3.recv(); if code == ~"" { break; } else { println!("Got back '{:s}'", code); } } }); } So now I’m adding a 3rd task and a 3rd channel to reverse the order of the text characters. Giving us…
  116. 125.

    fn transform(receiver: Receiver<~str>, sender: Sender<~str>, transformation: |~str| -> ~str) {

    loop { let code = receiver.recv(); if code == ~"" { sender.send(~""); break; } sender.send( transformation(code) ); } } ! fn main() { ... ! spawn(proc() { transform(receiver1, sender2, |x: ~str| x.to_ascii_upper()); }); ! spawn(proc() { transform(receiver2, sender3, |x: ~str| x.chars_rev().collect()); }); ! spawn(proc() { loop { let code = receiver3.recv(); if code == ~"" { break; } else { println!("Got back '{:s}'", code); } } }); } So now I’m adding a 3rd task and a 3rd channel to reverse the order of the text characters. Giving us…
  117. 126.

    use std::ascii::StrAsciiExt; ! fn transform(receiver: Receiver<~str>, sender: Sender<~str>, transformation: |~str|

    -> ~str) { loop { let code = receiver.recv(); if code == ~"" { sender.send(~""); break; } sender.send( transformation(code) ); } } ! fn main() { let (sender1, receiver1) = channel(); let (sender2, receiver2) = channel(); let (sender3, receiver3) = channel(); ! sender1.send(~"ycnerrucnoc hcus"); sender1.send(~"wow secar ynam"); sender1.send(~""); ! spawn(proc() { transform(receiver1, sender2, |x: ~str| x.to_ascii_upper()); }); ! spawn(proc() { transform(receiver2, sender3, |x: ~str| x.chars_rev().collect()); }); ! spawn(proc() { loop { let code = receiver3.recv(); if code == ~"" { break; } else { println!("Got back '{:s}'", code); } } }); } So now I’m adding a 3rd task and a 3rd channel to reverse the order of the text characters. Giving us…
  118. 127.

    use std::ascii::StrAsciiExt; ! fn transform(receiver: Receiver<~str>, sender: Sender<~str>, transformation: |~str|

    -> ~str) { loop { let code = receiver.recv(); if code == ~"" { sender.send(~""); break; } sender.send( transformation(code) ); } } ! fn main() { let (sender1, receiver1) = channel(); let (sender2, receiver2) = channel(); let (sender3, receiver3) = channel(); ! sender1.send(~"ycnerrucnoc hcus"); sender1.send(~"wow secar ynam"); sender1.send(~""); ! spawn(proc() { transform(receiver1, sender2, |x: ~str| x.to_ascii_upper()); }); ! spawn(proc() { transform(receiver2, sender3, |x: ~str| x.chars_rev().collect()); }); ! spawn(proc() { loop { let code = receiver3.recv(); if code == ~"" { break; } else { println!("Got back '{:s}'", code); } } }); } Got back 'SUCH CONCURRENCY' Got back 'MANY RACES WOW' So now I’m adding a 3rd task and a 3rd channel to reverse the order of the text characters. Giving us…
  119. 128.

    youtube: The Rust language: memory, ownership and lifetimes - Nicholas

    Matsakis ! http://rustforrubyists.com ! http://rust-lang.org gregmalcolm Learning Resources speakerdeck: gregmalcolm/rust-me-im-a-developer For those of you interested in looking into rust, at the moment, my suggestion for getting started is to look on youtube for a talk by Nicholas Matsakis which gives a really nice grounding on Rust. Next off I highly recommend Steve Klabnik’s Rust for Rubyists tutorial, even if you don’t know Ruby. It’s a lot easier to follow the current official tutorial. Next off look around the rust website for more.