Владимир Матвеев: "Rust: абстракции и безопасность, совершенно бесплатно" (Обзор языка Rust: для чего он предназначен, его ключевые особенности, инфраструктура)
Rust: абстракции и безопасность, совершенно бесплатно
1. Rust: абстракции и безопасность,
совершенно бесплатно
Владимир Матвеев
vmatveev@qubell.com
1 / 70
2. Место
Близко к железу, но большой простор для ошибок:
С
С++
Высокоуровневые, безопасные, но дают меньше контроля:
Java
Haskell
Python
Ruby
JS
Go
...
2 / 70
3. Классы ошибок
В C++:
Висящие указатели
Доступ за границами массивов
Утечки памяти
Переполнение буфера
Use-after-free
Гонки данных
Iterator invalidation
В Java:
NullPointerException
ConcurrentModificationException
Утечки памяти (!)
3 / 70
4. Выводы
Просто «Best practices» недостаточно
Безопасность с помощью идиом/гайдов не обеспечить
Компилятор должен сам отклонять небезопасные
программы
4 / 70
5. Решение
Rust обеспечивает безопасность работы с памятью с
помощью мощного статического анализа
Нет явного управления памятью, компилятор отслеживает
аллокации и деаллокации
Если программа компилируется, то она работает с
памятью безопасно
Zero-cost abstractions, как в С++
Вывод типов сильно помогает как при написании, так и
при чтении
5 / 70
6. История
Появился как личный проект
С 2009 спонсируется Mozilla Research
Первый анонс в 2010 году
В 2011 компилирует себя
В 2012 выходит версия 0.1
...
6 / 70
7. Будущее
Начало 2015 - релиз первой стабильной версии
Стабилизация языка и API
Центральный репозиторий пакетов
Множество отложенных фич
Типы высшего порядка
Ещё более продвинутые макросы
Вычисления при компиляции
...
7 / 70
8. Rust
компилируемый (LLVM)
быстрый
безопасный
со статической типизацией
с выводом типов
с бесплатными абстракциями
с функциональными элементами
минимальный рантайм (либо его отсутствие)
...
8 / 70
9. Кроссплатформенность
LLVM => множество платформ
Официально поддерживаются:
Linux
Mac OS X
Win32/Win64
Также работает под Android и iOS
9 / 70
12. Срезы
let array: [u8, ..16] = [0, ..16];
let slice: &[u8] = &array;
println!("{}", slice.len()); // 16
Строковые срезы (и вообще строки) всегда в UTF-8:
let s: &str = "abcde";
let str_buf: String = s.into_string();
12 / 70
17. С-подобный синтаксис
fn main() {
for i in range(0, 10) {
println!("Hello world!");
}
}
Всё — выражения
let m = if x % 2 == 0 { "even" } else { "odd" };
Вывод типов
fn take_int(x: int) { ... }
let x = 10;
take_int(x); // x is inferred as int
17 / 70
18. Циклы
let (mut x, mut y) = (random_int(), random_int());
loop {
x += 3;
if x < y { continue; }
y -= 3;
if x > y { break; }
}
let mut n = 0;
while n < 100 {
n += 1;
if n % 5 == 2 { n += 13; }
}
18 / 70
19. Сопоставление с образцом
switch как в C:
let x: uint = 10;
let name = match x {
1 => "one",
2 => "two",
_ => "many"
};
Деструктуризация как в Haskell:
let mut f = File::open("/tmp/input");
match f.read_to_end() {
Ok(content) => println!("{} bytes", content.len()),
Err(e) => println!("Error: {}", e)
}
19 / 70
20. Сопоставление с образцом
Для срезов:
let x = [1, 2, 3, 4];
match x.as_slice() {
[1, x, ..rest] => {
println!("2nd: {}, all others: {}", x, rest);
}
_ => println!("Something else")
}
При объявлении переменных и параметров:
fn sum_tuple((x, y): (int, int)) -> int {
x + y
}
20 / 70
21. Сопоставление с образцом
if let, while let из Swift:
if let Some(r) = from_str::<int>("12345") {
println!("String "12345" is {}", r);
}
while let Ok(token) = next_token() {
println!("Next token: {}", token);
}
21 / 70
22. Функции
fn multiply(left: uint, right: uint) -> uint {
left + right
}
#[no_mangle]
pub extern fn visible_from_c(arg: u32) -> u32 {
arg + arg
}
22 / 70
29. Трейты
Ср. с классами типов в Haskell:
class Display a where
display :: a -> String
class Add a rhs result where
add :: a -> rhs -> result
А также реализации для произвольных типов, множественная
диспетчеризация, ассоциированные типы, etc.
29 / 70
30. Trait objects
fn print_slice<T: Show>(items: &[T]) {
for item in items.iter() {
println!("{} ", item);
}
}
print_slice(&[1i, 2i, 3i]); // ok
print_slice(&["a", "b"]); // ok
print_slice(&[1i, 2i, "a"]); // compilation error :(
30 / 70
31. Trait objects
Трейты как интерфейсы:
fn print_slice(items: &[&Show]) {
for item in items.iter() {
println!("{}", item);
}
}
print_slice(&[&1i, &2i, &3i]); // ok
print_slice(&[&"a", &"b"]); // ok
print_slice(&[&1i, &2i, &"a"]); // ok!
31 / 70
32. Трейты
Zero-cost on-demand abstraction:
Ограничения на дженерики — статический полиморфизм,
мономорфизация, инлайнинг
Trait objects — динамический полиморфизм, виртуальные
таблицы, позднее связывание
Cost is explicit — сразу видно, где именно появляется
оверхед
32 / 70
34. Ownership and borrowing
Владение и заимствование — ключевые концепции Rust
С помощью статических проверок на их основе компилятор
способен предотвратить огромное число ошибок управления
ресурсами: use-after-free, double-free, iterator invalidation, data
races
Владение данными основывается на теории линейных типов
(linear types). Авторы Rust вдохновлялись языками Clean и
Cyclone; см. также unique_ptr в C++
34 / 70
35. Ownership
{
int *x = malloc(sizeof(int));
// do stuff
*x = 5;
free(x);
}
35 / 70
36. Ownership
{
int *x = malloc(sizeof(int));
// do stuff
*x = 5;
free(x);
}
{
let x = box 5;
}
36 / 70
37. Ownership
fn add_one(mut num: Box<int>) {
*num += 1;
}
let x = box 5i;
add_one(x);
println!("{}", x); // ! error: use of moved value: x
Move-семантика в действии!
37 / 70
38. Ownership
fn add_one(mut num: Box<int>) -> Box<int> {
*num += 1;
num
}
let x = box 5i;
let y = add_one(x);
println!("{}", y); // 6
38 / 70
39. Copy
Некоторые типы реализуют трейт Copy; они автоматически
копируются вместо перемещения:
let x: int = 10;
let y = x;
println!("{}", x);
39 / 70
40. RAII
Владение данными + move semantics + деструкторы =
безопасный RAII
{
let mut f = File::open(&Path::new("/some/path")).unwrap();
// work with file ...
} // f's destructor is called here
// (unless it is moved somewhere else)
Но move semantics подразумевает передачу права владения,
что возможно далеко не всегда:
let mut f = File::open(&Path::new("/some/path")).unwrap();
let buf = [0u8, ..128];
f.read(buf).unwrap();
println!("{}", buf); // ! use of moved value: buf
40 / 70
41. Borrowing
Владелец данных может предоставить к ним доступ с
помощью ссылок:
fn with_one(num: &int) -> int {
*num + 1
}
let x = box 5i;
println!("{}", with_one(&*x)); // 6
41 / 70
42. Borrowing
&T — разделяемые/иммутабельные (shared/immutable)
&mut T — неразделяемые/мутабельные (exclusive/mutable)
let x = 10i;
let p1 = &x;
let p2 = &x; // ok
let mut x = 10i;
let p1 = &mut x;
let p2 = &x; // ! cannot borrow x as immutable because
// ! it is also borrowed as mutable
let mut x = 10i;
let p1 = &mut x;
let p2 = &mut x; // ! cannot borrow x as mutable
// ! more than once at a time
42 / 70
43. Borrowing and mutability
«Эксклюзивность» мутабельных ссылок исключает очень
большой класс ошибок вида use-after-free (и не только):
let mut v: Vec<int> = vec![1, 2];
let e = &v[0];
v.push(3); // reallocates the vector, moving its contents
// ! cannot borrow v as mutable because
// ! it is also borrowed as immutable
let mut num = box 5i;
let e = &*num;
num = box 6i; // ! cannot assign to num because it is borrowed
let mut v = vec![1i, 2, 3];
for &e in v.iter() {
println!("{}", e);
if e == 2 { v.push(-42); } // ! cannot borrow v as mutable
}
43 / 70
44. Borrowing and mutability
Отсутствие неожиданностей:
fn do_stuff(data: &mut BigData, should_process: || -> bool) {
assert!(data.is_safe());
if should_process() {
unsafely_handle_data(data);
}
}
44 / 70
45. Borrowing and mutability
Наличие двух мутабельных ссылок позволяет реализовать
transmute() (aka reinterpret_cast) в безопасном коде:
fn my_transmute<T: Clone, U>(value: T, other: U) -> U {
let mut x = Left(other);
let y = match x {
Left(ref mut y) => y,
Right(_) => panic!()
};
x = Right(value);
y.clone()
}
45 / 70
46. Lifetimes
«Наивное» заимствование может вызвать проблемы:
1. создаётся ресурс X;
2. на ресурс X берётся ссылка a;
3. ресурс X уничтожается;
4. к [уничтоженному] ресурсу X осуществляется доступ через
ссылку a.
Use after free, доступ к закрытому файлу, etc.
Решение — статически обеспечить невозможность 4 перед 3.
46 / 70
47. Lifetimes
С каждой ссылкой ассоциирован параметр — время жизни
того объекта, на который она указывает. Компилятор
статически проверяет, что каждая ссылка всегда «живёт»
меньше, чем исходный объект:
fn make_ref<'a>() -> &'a int {
let x = 10i;
&x // ! x does not live long enough
}
fn first_and_last<'a>(slice: &'a [T]) -> (&'a T, &'a T) {
(&slice[0], &slice[slice.len()-1])
}
fn first_and_last(slice: &[T]) -> (&T, &T) { // identical
(&slice[0], &slice[slice.len()-1])
}
47 / 70
48. Lifetimes
Lifetime-параметры можно ассоциировать с областями
видимости:
let x;
{
let n = 5i;
x = &n; // ! n does not live long enough
}
println!("{}", *x);
48 / 70
50. Lifetimes
Специальный идентификатор 'static обозначает время жизни
всей программы:
static ANSWER: int = 42;
fn print_static_int_only(r: &'static int) { // '
println!("{}", *r);
}
fn main() {
print_static_int_only(&ANSWER); // ok
let r = 21;
print_static_int_only(&r); // ! r does not live long enough
}
const MESSAGE: &'static str = "Hello world!";
50 / 70
51. Shared ownership
В рамках одного потока — подсчёт ссылок:
use std::rc::Rc;
{
let r = Rc::new(vec![1, 2, 3]);
let r2 = r.clone();
println!("{}", *r);
println!("{}", *r2);
} // both references go out of scope, Vec is destroyed
51 / 70
53. Потоки
Создаются функцией spawn():
spawn(move || { // unboxed closure
println!("Hello from other thread!");
});
Потоки — это потоки ОС.
Система типов гарантирует, что замыкание не захватит
«опасные» переменные.
53 / 70
54. Каналы
Общение между потоками происходит через каналы:
let (tx, rx) = channel();
spawn(move || {
tx.send(4u + 6);
tx.send(5u + 7);
});
println!("{}, {}", rx.recv(), rx.recv());
54 / 70
55. Shared state
Данные «без ссылок внутри» разделяемые с помощью Arc:
use std::sync::Arc;
let data = Arc::new(vec![1u, 2, 3]);
let for_thread = data.clone();
spawn(move || {
println!("From spawned thread: {}", *for_thread);
});
println!("From main thread: {}", *data);
55 / 70
56. Mutable shared state
За счёт системы типов использование синхронизации
обязательно. Таким образом, исключаются гонки данных
(data races):
use std::sync::{Arc, Mutex};
let data = Arc::new(Mutex::new(vec![1u, 2, 3]));
let for_thread = data.clone();
spawn(move || {
let mut guard = for_thread.lock();
guard.push(4);
println!("{}", *guard);
});
let mut guard = data.lock();
guard.push(5);
println!("{}", *guard);
56 / 70
63. Единица компиляции — crate
pub mod a {
mod b {
// ...
}
pub mod c {
// ...
}
}
mod d {
// ...
}
На выходе — готовый бинарник (библиотека или executable)
63 / 70
64. Менеджер сборки — Cargo
разработан Yehuda Katz — автором Bundler
сборка и управление проектом:
отслеживание зависимостей
компиляция зависимостей, как на Rust, так и на C
компиляция проекта
запуск тестов, модульных и интеграционных
генерация пакетов и их деплой в репозиторий
reproducible builds
64 / 70
65. crates.io — центральный репозиторий
Открылся совсем недавно
Предназначен, в основном, для стабильных релизов
400 пакетов спустя полторы недели
Ядро будущей экосистемы
65 / 70
67. Servo — https://github.com/servo/
исследовательский браузерный движок
активно развивается, уже проходит какие-то тесты
~100000 строк
rustc — https://github.com/rust-lang/rust
сам компилятор Rust
самый старый крупный проект
~400000 строк
Cargo — https://github.com/rust-lang/cargo
менеджер сборки
один из наиболее новых проектов, idiomatic style
~30000 строк
67 / 70
68. Piston — https://github.com/PistonDevelopers
коллекция проектов, связанных с разработкой игр
байндинги к OpenGL и другим графическим (и не
только) библиотекам
игровой движок
GUI-библиотека
Zinc — https://zinc.rs
ARM-стек
эффективный и безопасный фреймворк для RTOS-
систем
~17000 строк
Iron — https://ironframework.org/
наиболее популярный веб-фреймворк (есть и другие!)
middleware-based
вместе с HTTP-библиотекой Hyper ~8000 строк
68 / 70