Rust is a new programming language that is growing rapidly. Rust's goal is to support a high-level coding style while offering performance comparable to C and C++ as well as minimal runtime requirements -- it does not require a runtime or garbage collector, and you can even choose to forego the standard library. At the same time, Rust offers strong support for parallel programming, including guaranteed freedom from data-races (something that GC’d languages like Java or Go do not provide).
Rust’s slim runtime requirements make it an ideal choice for integrating into other languages and projects. Anywhere that you could integrate a C or C++ library, you can choose to use Rust instead. Mozilla, for example, has rewritten a portion of the Firefox web browser in Rust -- while keeping the rest in C++. There are also projects for writing native extensions to Python, Ruby, and Node in Rust, as well as a recent effort to have the Rust compiler generate WebAssembly.
This talk will cover some of the highlights of Rust's design, and show how Rust's type system not only supports different parallel styles but also encourages users to write code that is amenable to parallelization. I'll also talk a bit about some of the experiences of using Rust in production, as well as how to integrate Rust into existing projects written in different languages.
5. 5
Static type system =
Eat your spinach!
Photo credit: Sanjoy Ghosh
https://www.flickr.com/photos/sanjoy/4016632253/
6. 6
Photo credit: Salim Virji
https://www.flickr.com/photos/salim/8594532469/
Static type system = Eat your spinach!
7. 7
The Rust compiler just saved me from a nasty threading bug. I was
working on cage (our open source development tool for Docker apps with
lots of microservices), and I decided to parallelize the routine that
transformed docker-compose.yml files.
9. static VALUE
rb_str_blank_as(VALUE str)
{
rb_encoding *enc;
char *s, *e;
enc = STR_ENC_GET(str);
s = RSTRING_PTR(str);
if (!s || RSTRING_LEN(str) == 0) return Qtrue;
e = RSTRING_END(str);
while (s < e) {
int n;
unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc);
switch (cc) {
case 9:
case 0xa:
case 0xb:
case 0xc:
case 0xd:
case 0x20:
case 0x85:
case 0xa0:
case 0x1680:
case 0x2000:
case 0x2001:
case 0x2002:
case 0x2003:
case 0x2004:
case 0x2005:
case 0x2006:
case 0x2007:
case 0x2008:
case 0x2009:
case 0x200a:
case 0x2028:
case 0x2029:
case 0x202f:
case 0x205f:
case 0x3000:
#if ruby_version_before_2_2()
case 0x180e:
#endif
/* found */
break;
default:
return Qfalse;
}
s += n;
}
return Qtrue;
}
Performance
Ruby:
964K iter/sec
C:
10.5M iter/sec
10x!
https://github.com/SamSaffron/fast_blank
10. Performance
10
class ::String
def blank?
/A[[:space:]]*z/ == self
end
end
extern “C” fn fast_blank(buf: Buf) -> bool {
buf.as_slice().chars().all(|c| c.is_whitespace())
}
Get Rust
string slice
Get iterator over
each character
Are all characters
whitespace?
Rust:
11M iter/sec
Ruby:
964K iter/sec
C:
10.5M iter/sec
12. 12
Q. What is Rust?
Q. Why should I care?
A. You can do more!
A. High-level code, low-level performance
13. 13
Experienced C++ hacker?
Prefer Ruby? JavaScript?
Make and maintain the designs you always
wanted — but could not justify.
Tune up your application and address hot-spots.
Lower memory usage. Add threads without fear.
14. 14
I like Rust because it is boring.
— CJ Silverio, npm CTO
21. fn main() {
let apple = Apple::new();
eat(apple);
}
21
fn eat(apple: Apple) {
…
}
Take ownership
of the apple
Give ownership
of the apple.
Owns
Owns
eat(apple);
~~~~~~~~~
Error: `apple` has
been moved.
22. fn main() {
let apple = Apple::new();
let mut bag = Vec::new();
bag.push(apple);
bag.push(Apple::new());
deliver(bag);
}
22
fn deliver(bag: Vec<Apple>) {
…
}
Take ownership
of the vector
Give ownership.
Give ownership.
Owns
Owns
23. 23
“Manual” memory management in Rust:
Values owned by creator.
Values moved via assignment.
When final owner returns, value is freed.
Feels invisible.
]
24. ~ Ownership and borrowing ~
Type Ownership
T
Alias? Mutate?
Owned ✓
&T Shared reference ✓
25. fn main() {
let apple = Apple::new();
let mut bag = Vec::new();
bag.push(apple);
bag.push(Apple::new());
let weight = weigh(&bag);
…
}
25
fn weigh(bag: &Vec<Apple>) -> u32 {
…
}
Shared reference
to the vector
Loan out the bag
(Return type)
shared
references
26. ~~~~~~~~~
let mut bag = Vec::new();
bag.push(…);
let r = &bag;
bag.len();
bag.push(…);
r.push(…);
bag.push(…);
reading `bag` ok while shared
cannot mutate while shared
26
Sharing “freezes” data (temporarily)
`bag` mutable here
~~~~~~~~~~~
`bag` borrowed here
after last use of `r`,
`bag` is mutable again
cannot mutate through shared ref
27. ~ Ownership and borrowing ~
Type Ownership
T
&T
Alias? Mutate?
Owned
Shared reference
✓
✓
&mut T Mutable reference ✓
28. cannot access `bag` while borrowed
but can mutate through `r`
28
Mutable references: no other access
`bag` mutable here
~~~~~~~~~
`bag` mutably borrowed here
after last use of `r`, `bag` is
accessible again
let mut bag = Vec::new();
bag.push(…);
let r = &mut bag;
bag.len();
r.push(…);
bag.push(…);
30. 30
Observation:
Building parallel abstractions is easy.
Misusing those abstractions is also easy.
func foo(…) {
m := make(map[string]string)
m[“Hello”] = “World”
channel <- m
m[“Hello”] = “Data Race”
}
send data over channel
but how to stop sender from
using it afterwards?
GoCode
31. 31
fn foo(…) {
let m = HashMap::new();
m.insert(“Hello”, “World”);
channel.send(m);
m.insert(“Hello”, “Data Race”);
}
impl<T> Channel<T> {
fn send(&mut self, data: T) {
…
}
}
Take ownership
of the data
Error: use of moved
value: `book`
~~~~~~~~~~~~~~~~~~~~~~~~~~
36. 36
fn load_images(paths: &[PathBuf]) -> Vec<Image> {
let mut jpgs = 0;
paths.par_iter()
.map(|path| {
if path.ends_with(“.jpg”) { jpgs += 1; }
Image::load(path)
})
.collect()
}
How many jpgs seen so far?
…add 1 to the counter.
If current file name ends in “jpg”…
0
0
+ 1
0
+ 1
1 1
1
46. 46
ArrayBuffer fn callback(mut vm: CallContext) {
let buffer = vm.arguments(0);
let guard: VmGuard = vm.lock();
let data = buffer.borrow_mut(&guard);
data
.par_iter_mut()
.for_each(|i| *i += 1);
}
vm
guard
borrows
data
buffer
data
47. 47
ArrayBuffer
buffer
data
What could go wrong?
What if we invoked JS
callback while using `data`?
▶ Can’t: Invoking JS
callbacks requires mutable
borrow of `vm`!
vm
guard
borrow
data
58. 58
"Because our product helps people identify why their
apps are slow, it is very important that we
ourselves do not make their app slow," says
Yehuda Katz, CTO of Tilde.
Performance monitoring software for RoR.
Written (initially) in Ruby, but hit performance limitations:
59. 59
Why Rust?
Next, they prototyped the agent in C++. Although the
C++ implementation used fewer resources, it also
introduced an unacceptable risk of crashes.
Katz and his team spent time squeezing every drop
of performance out of Ruby including removing
uses of higher-level features of the language that
required more resources.
A. C++ without crashes means the whole
team can write performance critical code.
64. 64
Rust Teams
Core Team
Language Team
Library Team
Compiler Team
Dev Tools Team
Cargo Team
IDEs and Editor Team
Infrastructure Team
Release Team
Community Team
Documentation Team
Rustdoc Team
Moderation Team
Mostly open-source volunteers.
Mozilla employees are a small fraction.