Креирано 2024-11-06 Wed 13:16, притисни ESC за мапу, Ctrl+Shift+F за претрагу, "?" за помоћ
rustup
$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
~> rustc --version
rustc 1.65.0 (897e37553 2022-11-02)
~> rustup show
Default host: x86_64-unknown-linux-gnu
rustup home: /home/igor/.rustup
installed toolchains
--------------------
stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu
active toolchain
----------------
stable-x86_64-unknown-linux-gnu (default)
rustc 1.58.1 (db9d1b20b 2022-01-20)
error: | unexpected | argument | 'home/igor.rustup' | found |
Usage: | rustup | [OPTIONS] | [+toolchain] | [COMMAND] |
For | more | information, | try | '–help'. |
За ажурирање инсталације на најновију верзију:
rustup update
Раст стиже са веома добром документацијом и књигама које су доступне директно из инсталације:
rustup doc
$ mkdir hello_world
$ cd hello_world
File main.rs
:
fn main() {
println!("Hello, world!");
}
$ rustc main.rs
$ ./main
Hello, world!
~> cargo --version
cargo 1.65.0 (4bc8f24d3 2022-10-20)
cargo
алатом:$ cargo new hello_cargo
$ cd hello_cargo
Фајл cargo.toml
чува конфигурацију пројекта. Формат је TOML (Tom’s Obvious,
Minimal Language):
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"
[dependencies]
Fajl src/main.rs
:
fn main() {
println!("Hello, world!");
}
$ cargo build
Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
Finished dev [unoptimized + debuginfo] target(s) in 2.85 secs
Извршни фајл се може наћи на локацији target/debug/hello_cargo
:
$ ./target/debug/hello_cargo
Hello, world!
Али cargo
омогућава и једноставнији начин покретања:
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/hello_cargo`
Hello, world!
Такође је могуће брзо проверити да ли се код компајлира:
$ cargo check
Checking hello_cargo v0.1.0 (file:///projects/hello_cargo)
Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs
За финалну верзију је потребно изградњу обавити на следећи начин:
cargo build --release
cargo
као конвенција
Практично сви Раст пројекти користе cargo
тако да је унификован начин изградње
пројеката. Углавном се своди на:
$ git clone example.org/someproject
$ cd someproject
$ cargo build
$ cargo new guessing_game
$ cd guessing_game
Фајл Cargo.toml
:
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
[dependencies]
Фајл src/main.rs
:
fn main() {
println!("Hello, world!");
}
$ cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 1.50s
Running `target/debug/guessing_game`
Hello, world!
use std::io;
fn main() {
println!("Guess the number!");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
Уколико желимо варијаблу чија вредност може да се мења користимо кључну реч
mut
.
let apples = 5; // immutable
let mut bananas = 5; // mutable
Исто важи и за параметре и аргументе функција:
io::stdin()
.read_line(&mut guess)
Result
типа
read_line
може да заврши неуспешно. Зато враћа io::Result
тип који представља
тип енумерације (enum) и има две могуће вредности: Ok
и Err
.
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
expect
je имплементиран тако да ће у случају Ok
варијанте вратити вредност која
је садржана унутар варијанте док ће у случају Err
варијанте прекинути извршавање
програма.
pub fn expect(self, msg: &str) -> T
where
E: fmt::Debug,
{
match self {
Ok(t) => t,
Err(e) => unwrap_failed(msg, &e),
}
}
$ cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 6.44s
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
6
You guessed: 6
Користимо rand
пакет (сандук - crate у терминологији Cargo-a).
Секција [dependencies]
у фајлу Cargo.toml
:
[dependencies]
rand = "0.8.3"
$ cargo build
Updating crates.io index
Downloaded rand v0.8.3
Downloaded libc v0.2.86
...
Compiling rand_chacha v0.3.0
Compiling rand v0.8.3
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 2.53s
Cargo користи Semantic Versioning. Раст пакети се преузимају са сајта crates.io и кеширају локално.
cargo build
креира се фајл Cargo.lock
са
информацијама о верзијама свих сандука који су инсталирани.Cargo.lock
је потребно чувати у систему контроле верзија (нпр. git
) да би се
осигурала поновљивост.Ажурирање на нове верзије сандука се обавља са:
$ cargo update
Updating crates.io index
Updating rand v0.8.3 -> v0.8.4
Cargo.toml
фајлу.
Фајл src/main.rs
:
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..101);
println!("The secret number is: {}", secret_number);
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
У gen_range
користимо израз опсега (range expression). Интервал је затворен на
доњој граници и отворен на горњој. За интервал затворен и од горе можемо
писати 1..=100
.
Да би знали које методе и функције су нам доступне можемо користити уграђену документацију за сандуке пројекта. Документацију добијамо са:
cargo doc --open
$ cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 2.53s
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 7
Please input your guess.
4
You guessed: 4
Фајл src/main.rs
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
// --snip--
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
std::cmp::Ordering
енумерисани тип који има варијанте Less
, Greater
и Equal
match
израз пореди задату вредност са вредностима задатим у телу и извршава
грану која се подудара. Гране match
израза се у Раст терминологији зову "руке"
(arms).Код са претходног слајда није исправан:
$ cargo build
Compiling libc v0.2.86
...
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
error[E0308]: mismatched types
--> src/main.rs:22:21
|
22 | match guess.cmp(&secret_number) {
| ^^^^^^^^^^^^^^ expected struct `String`, found integer
|
= note: expected reference `&String`
found reference `&{integer}`
error[E0283]: type annotations needed for `{integer}`
--> src/main.rs:8:44
|
8 | let secret_number = rand::thread_rng().gen_range(1..101);
| ------------- ^^^^^^^^^ cannot infer type for type `{integer}`
| |
| consider giving `secret_number` a type
|
= note: multiple `impl`s satisfying `{integer}: SampleUniform` found in the `rand` crate:
- impl SampleUniform for i128;
- impl SampleUniform for i16;
- impl SampleUniform for i32;
- impl SampleUniform for i64;
and 8 more
...
Основа грешке је неслагање типова. Са улаза прихватамо String
док нам је тајни
број integer
.
Проблем решавамо конверзијом стринга са улаза у бројни тип.
// --snip--
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
Сада се програм компајлира.
$ cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.43s
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 58
Please input your guess.
76
You guessed: 76
Too big!
Фајл src/main.rs
:
// --snip--
println!("The secret number is: {}", secret_number);
loop {
println!("Please input your guess.");
// --snip--
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
}
Проблем је како прекинути програм када корисник погоди број?
// --snip--
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
// --snip--
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {}", guess);
// --snip--
Потребно је још обрисати линију која приказује генерисани број.
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..101);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
Једном када добију вредност (binding) та вредност се не може променити
fn main() {
let x = 5;
println!("The value of x is: {}", x);
x = 6; // greška
println!("The value of x is: {}", x);
}
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| -
| |
| first assignment to `x`
| help: consider making this binding mutable: `mut x`
3 | println!("The value of x is: {}", x);
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` due to previous error
mut
кључна речfn main() {
let mut x = 5;
println!("The value of x is: {}", x);
x = 6;
println!("The value of x is: {}", x);
}
The value of x is: 5 The value of x is: 6
const
По конвенцији имена константи се пишу великим словима
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
Компајлер ће пробати да одреди типове (type inference). Ако није могуће захтева се да дефинишемо тип експлицитно.
Нпр:
let guess: u32 = "42".parse().expect("Not a number!");
Овде није могуће одредити тип јер str::parse
функција може вратити различите
бројне типове (функција је генеричка) а не постоји начин да се тип аутоматски
одреди.
Сигнатура је:
pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>
where
F: FromStr
Дужина | Signed | Unsigned |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
Литерали | Примери |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1111_0000 |
Byte (u8 only) | b'A' |
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
IEEE-754 стандард
fn main() {
// addition
let sum = 5 + 10;
// subtraction
let difference = 95.5 - 4.3;
// multiplication
let product = 4 * 30;
// division
let quotient = 56.7 / 32.2;
let floored = 2 / 3; // Results in 0
// remainder
let remainder = 43 % 5;
}
fn main() {
let t = true;
let f: bool = false; // with explicit type annotation
}
fn main() {
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
}
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
}
The value of y is: 6.4
Приступ елементима торке:
fn main() {
let mut x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
// Уколико је торка промењива можемо извршити доделу
x.2 = 2;
println!("{x:?}")
}
(500, 6.4, 2)
Димензија низа је непромењива (алоциран је на стеку)
fn main() {
let a = [1, 2, 3, 4, 5];
}
Тип се може експлицитно дефинисати на следећи начин (низ дужине 5
типа i32
):
let a: [i32; 5] = [1, 2, 3, 4, 5];
Иницијализација свих елемената на исту вредност се обавља на следећи начин:
let a = [3; 5];
Где је вредност сваког елемента 3
а дужина низа 5
.
Индексни приступ:
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
Конверзија низа у хомогену торку:
fn main() {
let a = [1, 2, 3];
let tup: (u32, u32, u32) = a.into();
println!("{tup:?}")
}
(1, 2, 3)
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
Hello, world! Another function.
fn main() {
print_labeled_measurement(5, 'h');
}
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {}{}", value, unit_label);
}
The measurement is: 5h
Пример: let
је исказ тј. нема повратну вредност. Ово можете писати:
let y = 6;
Ali ovo ne:
let x = (let y = 6);
Блок кода је такође израз. Шта је вредност у коју се евалуира?
{
let x = 3;
x + 1
}
Вредност блока је вредност последњег израза, тј. x+1
. Приметите да ту не
користимо ;
јер терминација овим карактером претвара израз у исказ.
Због овога је сасвим легално да пишемо:
fn main() {
let y = {
let x = 3;
x + 1
};
println!("The value of y is: {}", y);
}
The value of y is: 4
Вредност функције је вредност блока који представља тело функције, дакле последњег израза унутар тела функције.
fn five() -> i32 {
5
}
fn main() {
let x = five();
println!("The value of x is: {}", x);
}
The value of x is: 5
Или на пример:
fn main() {
let x = plus_one(5);
println!("The value of x is: {}", x);
}
fn plus_one(x: i32) -> i32 {
x + 1
}
The value of x is: 6
Уколико израз x+1
терминирамо са ;
код се неће компајлирати јер функција
декларише да враћа тип i32
док сада враћа ()
(тзв. unit type) односно нема
повратну вредност јер је последња инструкција исказ.
fn main() {
let x = plus_one(5);
println!("The value of x is: {}", x);
}
fn plus_one(x: i32) -> i32 {
x + 1;
}
error[E0308]: mismatched types --> src/main.rs:8:24 | 8 | fn plus_one(x: i32) -> i32 { | -------- ^^^ expected `i32`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression 9 | x + 1; | - help: consider removing this semicolon For more information about this error, try `rustc --explain E0308`. error: could not compile `cargo4UyKF0` due to previous error
Коментари се пишу после //
или у форми блок коментара /*.... */
као и у C++-у.
Ово би били валидни коментари:
// So we’re doing something complicated here, long enough that we need
// multiple lines of comments to do it! Whew! Hopefully, this comment will
// explain what’s going on.
fn main() {
let lucky_number = 7; // I’m feeling lucky today
}
...
fn main() {
// I’m feeling lucky today
let lucky_number = 7;
/* this is block comment
which can span multiple lines.
Nesting is allowed.
*/
}
Постоје и коментари за документацију који представљају посебну синтаксу за
коментаре који су део API документације. Они се пишу после ///
. На пример:
/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}
if
изразиfn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
condition was true
if-else
fn main() {
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
}
number is divisible by 3
if
у let
исказима
if
је израз па се може користити где год можемо писати и било који други израз.
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {}", number);
}
The value of number is: 5
Али се гране морају слагати по типу. Ово је погрешно јер је прва грана типа
i32
док је else
грана типа str
.
fn main() {
let condition = true;
let number = if condition { 5 } else { "six" };
println!("The value of number is: {}", number);
}
error[E0308]: `if` and `else` have incompatible types --> src/main.rs:5:44 | 5 | let number = if condition { 5 } else { "six" }; | - ^^^^^ expected integer, found `&str` | | | expected because of this For more information about this error, try `rustc --explain E0308`. error: could not compile `cargoF7mlQA` (bin "cargoF7mlQA") due to 1 previous error
Рaст има три типа петље:
loop
- за бесконачне петљеwhile
- условна петљаfor
- петља за итерацију кроз елементе итерабилних типоваloop
fn main() {
loop {
println!("again!");
}
}
loop
лабеле
Уколико имамо угњеждене loop
исказе можемо користити лабеле приликом изласка са
break
инструкцијом.
fn main() {
let mut count = 0;
'counting_up: loop {
println!("count = {}", count);
let mut remaining = 10;
loop {
println!("remaining = {}", remaining);
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up;
}
remaining -= 1;
}
count += 1;
}
println!("End count = {}", count);
}
count = 0 remaining = 10 remaining = 9 count = 1 remaining = 10 remaining = 9 count = 2 remaining = 10 End count = 2
loop
као израз
loop
може имати повратну вредност. Повратна вредност се дефинише као параметар
break
инструкције.
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
The result is 20
while
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
}
3! 2! 1! LIFTOFF!!!
for
Итерацију кроз уређену колекцију, као што је низ, можемо обавити са while
петљом.
fn main() {
let a = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("the value is: {}", a[index]);
index += 1;
}
}
the value is: 10 the value is: 20 the value is: 30 the value is: 40 the value is: 50
Али је за ту намену природније и сигурније користити for
петљу. Такође ће се
програм брже извршавати.
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {}", element);
}
}
the value is: 10 the value is: 20 the value is: 30 the value is: 40 the value is: 50
for
петље су најчешћи облик петљи у употреби у Расту. Користе се нпр. и у
ситуацији када је потребно извршити петљу одређени број пута.
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
3! 2! 1! LIFTOFF!!!
for
и итератори
Приликом итерације употребом for
исказа имплицитно се позива метода
into_iter()
над колекцијом. Ова метода конзумира елементе колекције.
fn main() {
let names = vec!["Bob", "Frank", "Ferris"];
for name in names.into_iter() {
match name {
"Ferris" => println!("There is a rustacean among us!"),
_ => println!("Hello {}", name),
}
}
// println!("names: {:?}", names); -- FIXME: колекција овде није доступна
}
Hello Bob Hello Frank There is a rustacean among us!
fn main() {
let names = vec!["Bob", "Frank", "Ferris"];
for name in names.iter() {
match name {
&"Ferris" => println!("There is a rustacean among us!"),
// TODO ^ Try deleting the & and matching just "Ferris"
_ => println!("Hello {}", name),
}
}
println!("names: {:?}", names);
}
Hello Bob Hello Frank There is a rustacean among us! names: ["Bob", "Frank", "Ferris"]
fn main() {
let mut names = vec!["Bob", "Frank", "Ferris"];
for name in names.iter_mut() {
*name = match name {
&mut "Ferris" => "There is a rustacean among us!",
_ => "Hello",
}
}
println!("names: {:?}", names);
}
names: ["Hello", "Hello", "There is a rustacean among us!"]
{ // s није валидно овде јер још није декларисано
let s = "hello"; // s је валидно од ове позиције
// користимо s
} // овде опсег престаје да важи и s више није валидно
String
типДемонстрација власништва над типом који се алоцира на хипу.
let mut s = String::from("hello");
s.push_str(", world!"); // push_str() додаје литерал на стринг
println!("{}", s); // hello, world!`
Алокација стринга се обавља на линији:
let mut s = String::from("hello");
Позива се функција drop
над типом и ова функција је задужена да обави деалокацију.
{
let s = String::from("hello"); // s постаје валидно
// користимо s
} // <- s излази из опсега и позива се "drop"
Копирање вредности x
у y
. Обе варијабле сада имају вредност 5
.
let x = 5;
let y = x;
Али са String
типом дешава се нешто друго.
let s1 = String::from("hello");
Ако би се копирао само садржај са стека имали бисмо следећу ситуацију (тзв. shallow copy).
let s1 = String::from("hello");
let s2 = s1;
Што је проблематично јер када и s1
и s2
напусте опсег покушаће се двострука
деалокација исте меморије на хипу (double free).
Ако би се и хип меморија копирала (tzv. deep copy) имали бисмо валидну ситуацију али би таква операција била веома "скупа".
Ако пробамо да компајлирамо следећи код:
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
Добићемо грешку:
$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0382]: borrow of moved value: `s1` --> src/main.rs:5:28 | 2 | let s1 = String::from("hello"); | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait 3 | let s2 = s1; | -- value moved here 4 | 5 | println!("{}, world!", s1); | ^^ value borrowed here after move For more information about this error, try `rustc --explain E0382`. error: could not compile `ownership` due to previous error
Оно што нам компајлер каже је да се у исказу:
let s2 = s1;
променио власник овог стринга. Нови власник је сада s2
док је варијабла s1
постала невалидна и није је више могуће користити.
Кажемо да се обавило "премештање" (move) вредности из s1
у s2
.
Чиме се испуњава прво правило власништва и спречава double free грешка.
Последица претходног је да Раст никада неће аутоматски обавити дубоко копирање варијабле јер би то могло да изазове лоше перформансе.
Дубоко копирање (стек+хип) радимо са clone
методом:
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
Сада је лако пронаћи у коду сва места где се обавља потенцијално "скупа" операција дубоког копирања.
Како онда ради пример који смо видели претходно? Зашто не долази до премештања
и инвалидације y
варијабле?
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
fn main() {
let s = String::from("hello"); // s постаје валидно
takes_ownership(s); // s вредност се премешта у функцију...
// ... тако да s није валидно од ове позиције
let x = 5; // x постаје валидно
makes_copy(x); // x би се преместило у функцију,
// али i32 је Copy, тако да је ok
// да се x користи и после
} // x излази из опсега, затим s. Али пошто је s премештено ништа посебно се
// не дешава.
fn takes_ownership(some_string: String) { // some_string улази у опсег
println!("{}", some_string);
} // some_string излази из опсега и позива се `drop`. Меморија са хипа се
// ослобађа.
fn makes_copy(some_integer: i32) { // some_integer улази у опсег
println!("{}", some_integer);
} // some_integer излази из опсега. Пошто није 'Drop', ништа посебно се не
// догађа (осим "скидања" са стека наравно)
Приликом враћања вредности из функције такође може доћи до премештања власништва.
fn main() {
let s1 = gives_ownership(); // gives_ownership премешта повратну
// вредност у s1
let s2 = String::from("hello"); // s2 постаје валидно
let s3 = takes_and_gives_back(s2); // s2 се премешта у функцију
// takes_and_gives_back, која премешта
// повратну вредност у s3
} // s3 излази из опсега и позива се `drop`. s2 је премештена па се ништа
// не дешава. s1 такође излази из опсега и деалоцира се.
fn gives_ownership() -> String { // gives_ownership ће преместити
// своју повратну вредност у функцију
// која је позива
let some_string = String::from("yours"); // some_string постаје валидно
some_string // some_string се премешта
// у функцију позиваоца
}
// Ова функција узима власништво над стрингом и враћа га назад
fn takes_and_gives_back(a_string: String) -> String { // a_string постаје валидно
a_string // a_string се премешта у функцију позиваоца
}
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
(s, length)
}
Али је то напорно. Постоји концепт у Раст који је намењен оваквим ситуацијама и базиран је на референцама и позајмљивању вредности.
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
Примена оператора &
над варијаблом називамо референцирање.
let s1 = String::from("hello");
let len = calculate_length(&s1);
&s1
нам омогућава да креирамо референцу на вредност чији власник је
s1
без узимања власништва.*
оператором над референцом (нпр. *s2
је вредност на коју референцира s2
).fn calculate_length(s: &String) -> usize { // s је референца на String
s.len()
} // s излази из опсега али пошто нема власништво над вредношћу
// ништа се не дешава.
Шта се дешава уколико покушамо да модификујемо позајмљену вредност?
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference --> src/main.rs:8:5 | 7 | fn change(some_string: &String) { | ------- help: consider changing this to be a mutable reference: `&mut String` 8 | some_string.push_str(", world"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable For more information about this error, try `rustc --explain E0596`. error: could not compile `ownership` due to previous error
Као и варијабле, и референце су подразумевано непромењива (immutable). Морамо бити експлицитни уколико нам треба промењива референца.
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
Раст не дозвољава да исти податак у једном тренутку има више промењивих референци.
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0499]: cannot borrow `s` as mutable more than once at a time --> src/main.rs:5:14 | 4 | let r1 = &mut s; | ------ first mutable borrow occurs here 5 | let r2 = &mut s; | ^^^^^^ second mutable borrow occurs here 6 | 7 | println!("{}, {}", r1, r2); | -- first borrow later used here For more information about this error, try `rustc --explain E0499`. error: could not compile `ownership` due to previous error
Овим ограничењем Раст, у време компајлирања, спречава класу грешака које доводе до недефинисаног понашања и које зовемо data races. Ове грешке се веома тешко откривају и отклањају и могу настати уколико су задовољени следећи услови:
Више промењивих референци можемо имати али не у истом опсегу:
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 овде излази из опсега тако да можемо краирати нове референце
let r2 = &mut s;
Слично правило постоји и уколико имамо комбинацију промењивих и непромењивих референци:
let mut s = String::from("hello");
let r1 = &s; // ovo je OK
let r2 = &s; // ovo je OK
let r3 = &mut s; // GREŠKA!
println!("{}, {}, and {}", r1, r2, r3);
$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable --> src/main.rs:6:14 | 4 | let r1 = &s; // no problem | -- immutable borrow occurs here 5 | let r2 = &s; // no problem 6 | let r3 = &mut s; // BIG PROBLEM | ^^^^^^ mutable borrow occurs here 7 | 8 | println!("{}, {}, and {}", r1, r2, r3); | -- immutable borrow later used here For more information about this error, try `rustc --explain E0502`. error: could not compile `ownership` due to previous error
Опсег важења референце почиње од места где је уведена па до њене последње употребе. На пример, ово је валидно:
let mut s = String::from("hello");
let r1 = &s; // OK
let r2 = &s; // OK
println!("{} and {}", r1, r2);
// варијабле r1 и r2 се не користе у наставку па њихов опсег
// престаје да важи.
let r3 = &mut s; // зато је ово OK
println!("{}", r3);
Ова особина референци се назива Non-Lexical Lifetimes (NLL).
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0106]: missing lifetime specifier --> src/main.rs:5:16 | 5 | fn dangle() -> &String { | ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from help: consider using the `'static` lifetime | 5 | fn dangle() -> &'static String { | ~~~~~~~~ For more information about this error, try `rustc --explain E0106`. error: could not compile `ownership` due to previous error
У преводу Раст нам поручује:
Тип повратне вредности ове функције је позајмљена вредност, али не постоји улазни параметар од кога може да се позајми.
fn dangle() -> &String { // dangle враћа референцу на String
let s = String::from("hello"); // s је нови String
&s // Враћамо референцу на String s
} // s излази из опсега и позива се `drop`. Меморија се ослобађа.
// ОПАСНОСТ! Враћена референца ће бити "висећа".
Како се може решити. Вратићемо поседовану (owned) вредност, тј урадићемо премештање вредности у функцију позиваоца.
fn no_dangle() -> String {
let s = String::from("hello");
s
}
Функција која за задати стринг враћа прву реч.
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s); // word ће добити вредност 5
s.clear(); // стринг s постаје ""
// word је и даље 5 иако то више није исправно
}
Проблем постаје још озбиљнији ако нпр. напишемо функцију second_word
која враћа
другу реч задатог стринга. По аналогији са претходним, требали би да вратимо
почетак и крај друге речи.
fn second_word(s: &String) -> (usize, usize) {
Сада имамо три податка о којима морамо да водимо рачуна.
Проблем решавамо употребом исечака.
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
Синтакса s[x..y]
креира исечак над секвенцом s
. С обзиром да овај тип ([T]
) нема
познату величину у време компајлирања, не можемо га алоцирати на стеку, односно
не можемо га доделити локалним варијаблама или прослеђивати као параметар
функције. Зато се у пракси користи референца на исечак (&[T]
) (често се зове и
fat pointer).
Дакле, у пракси много чешће срећемо синтаксу &s[x..y]
.
let s = String::from("hello");
let slice = &s[0..2];
let slice = &s[..2];
let s = String::from("hello");
let len = s.len();
let slice = &s[3..len];
let slice = &s[3..];
let s = String::from("hello");
let len = s.len();
let slice = &s[0..len];
let slice = &s[..];
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
Исти API и за second_word
.
fn second_word(s: &String) -> &str {
Сада нам компајлер осигурава да увек имамо валидну референцу на реч.
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // ГРЕШКА!
println!("the first word is: {}", word);
}
$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable --> src/main.rs:18:5 | 16 | let word = first_word(&s); | -- immutable borrow occurs here 17 | 18 | s.clear(); // error! | ^^^^^^^^^ mutable borrow occurs here 19 | 20 | println!("the first word is: {}", word); | ---- immutable borrow later used here For more information about this error, try `rustc --explain E0502`. error: could not compile `ownership` due to previous error
Не само да је API лакши за употребу већ је читава класа грешака које се тешко откривају елиминисана у време компајлирања.
let s = "Hello, world!";
Тип од s
је &str
. str
је стринг исечак.
fn first_word(s: &String) -> &str {
Али ће искусни Раст програмери писати:
fn first_word(s: &str) -> &str {
Јер ће ова функција моћи да се користи и за &str
и за &String
. Овде се
употребљава тзв. Deref Coercion односно могућности типова да се дереференцирају
у други тип. Нпр. &String
ће се аутоматски дереференцирати у &str
уколико је то
потребно. Операција је ефикасна.
fn main() {
let my_string = String::from("hello world");
// `first_word` works on slices of `String`s, whether partial or whole
let word = first_word(&my_string[0..6]);
let word = first_word(&my_string[..]);
// `first_word` also works on references to `String`s, which are equivalent
// to whole slices of `String`s
let word = first_word(&my_string);
let my_string_literal = "hello world";
// `first_word` works on slices of string literals, whether partial or whole
let word = first_word(&my_string_literal[0..6]);
let word = first_word(&my_string_literal[..]);
// Because string literals *are* string slices already,
// this works too, without the slice syntax!
let word = first_word(my_string_literal);
}
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // референца на исечак типа &[i32]
assert_eq!(slice, &[2, 3]);
struct
типКориснички тип који омогућава груписање више логички повезаних вредности - поља (fields).
У Расту постоје три врсте структура:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
}
fn main() {
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
}
fn build_user(email: String, username: String) -> User {
User {
active: true,
username: username,
email: email,
sign_in_count: 1,
}
}
fn build_user(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1,
}
}
Уместо:
fn main() {
// --snip--
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
}
Можемо писати:
fn main() {
let user2 = User {
email: String::from("another@example.com"),
..user1
};
}
user1.username
је премештен у user2
и не може више да се користи. Остатак
структуре може јер user1.email
није премештен а остала поља су Copy типа.
Тзв. именоване торке. Поља немају имена, приступа им се као код обичних торки
употребом x.n
синтаксе где је n
редни број поља.
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(5, 8, 0);
let y = origin.1;
println!("{}", y);
}
8
Користе се за креирање типова који имају понашање (имплементирају особине) али не чувају податке (тзв. Zero Sized Types (ZSTs))
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
// A unit struct
struct Unit;
// A tuple struct
struct Pair(i32, f32);
// A struct with two fields
struct Point {
x: f32,
y: f32,
}
// Structs can be reused as fields of another struct
struct Rectangle {
// A rectangle can be specified by where the top left and bottom right
// corners are in space.
top_left: Point,
bottom_right: Point,
}
// Create struct with field init shorthand
let name = String::from("Peter");
let age = 27;
let peter = Person { name, age };
// Print debug struct
println!("{:?}", peter);
// Instantiate a `Point`
let point: Point = Point { x: 10.3, y: 0.4 };
// Access the fields of the point
println!("point coordinates: ({}, {})", point.x, point.y);
// Make a new point by using struct update syntax to use the fields of our
// other one
let bottom_right = Point { x: 5.2, ..point };
// `bottom_right.y` will be the same as `point.y` because we used that field
// from `point`
println!("second point: ({}, {})", bottom_right.x, bottom_right.y);
// Destructure the point using a `let` binding
let Point { x: left_edge, y: top_edge } = point;
let _rectangle = Rectangle {
// struct instantiation is an expression too
top_left: Point { x: left_edge, y: top_edge },
bottom_right: bottom_right,
};
// Instantiate a unit struct
let _unit = Unit;
// Instantiate a tuple struct
let pair = Pair(1, 0.1);
// Access the fields of a tuple struct
println!("pair contains {:?} and {:?}", pair.0, pair.1);
// Destructure a tuple struct
let Pair(integer, decimal) = pair;
println!("pair contains {:?} and {:?}", integer, decimal);
192.0.2.1
:
, нпр. 2001:db8:0:1234:0:567:8:1
У Расту можемо писати следеће:
enum IpAddrKind {
V4,
V6,
}
Док вредности можемо креирати са:
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
Сада можемо писати функцију која прима овај тип чиме је могуће проследити било коју варијанту као аргумент.
fn route(ip_kind: IpAddrKind) {}
...
route(IpAddrKind::V4);
route(IpAddrKind::V6);
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from("::1"),
};
Али, са enum
типом можемо то урадити и боље. Enum
варијанте могу садржати
додатне податке.
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
Вредност садржана у варијанти не мора бити иста за све варијанте. На пример, IPv4 се састоји од 4 октета и можда желимо да вредност наводимо и чувамо у том облику.
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
У стандардној библиотеци можемо пронаћи тип IpAddr
. Дефинисан је на следећи
начин.
struct Ipv4Addr {
// --snip--
}
struct Ipv6Addr {
// --snip--
}
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
Следеће структуре садрже исте податке као претходни enum
тип.
struct QuitMessage; // unit struct
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct
Али, предност enum
типа је што је то јединствен тип па можемо нпр. направити
функцију која прима било коју вредност/варијанту овог типа.
Над enum
типом, као и другим типовима, можемо имплементирати методе употребом
imlp
кључне речи.
impl Message {
fn call(&self) {
// тело методе се овде дефинише
}
}
let m = Message::Write(String::from("hello"));
m.call();
enum WebEvent {
// An `enum` variant may either be `unit-like`,
PageLoad,
PageUnload,
// like tuple structs,
KeyPress(char),
Paste(String),
// or c-like structures.
Click { x: i64, y: i64 },
}
fn inspect(event: WebEvent) {
match event {
WebEvent::PageLoad => println!("page loaded"),
WebEvent::PageUnload => println!("page unloaded"),
// Destructure `c` from inside the `enum` variant.
WebEvent::KeyPress(c) => println!("pressed '{}'.", c),
WebEvent::Paste(s) => println!("pasted \"{}\".", s),
// Destructure `Click` into `x` and `y`.
WebEvent::Click { x, y } => {
println!("clicked at x={}, y={}.", x, y);
},
}
}
fn main() {
let pressed = WebEvent::KeyPress('x');
// `to_owned()` creates an owned `String` from a string slice.
let pasted = WebEvent::Paste("my text".to_owned());
let click = WebEvent::Click { x: 20, y: 80 };
let load = WebEvent::PageLoad;
let unload = WebEvent::PageUnload;
inspect(pressed);
inspect(pasted);
inspect(click);
inspect(load);
inspect(unload);
}
pressed 'x'. pasted "my text". clicked at x=20, y=80. page loaded page unloaded
use
кључне речиВаријанте можемо импортовати у текући опсег тако да се може избећи пуна квалификација варијанти енумерисаног типа.
enum Status {
Rich,
Poor,
}
enum Work {
Civilian,
Soldier,
}
fn main() {
// Explicitly `use` each name so they are available without
// manual scoping.
use crate::Status::{Poor, Rich};
// Automatically `use` each name inside `Work`.
use crate::Work::*;
// Equivalent to `Status::Poor`.
let status = Poor;
// Equivalent to `Work::Civilian`.
let work = Civilian;
match status {
// Note the lack of scoping because of the explicit `use` above.
Rich => println!("The rich have lots of money!"),
Poor => println!("The poor have no money..."),
}
match work {
// Note again the lack of scoping.
Civilian => println!("Civilians work!"),
Soldier => println!("Soldiers fight!"),
}
}
Енумерисани тип у Расту се може користити као у Ц-у.
// enum with implicit discriminator (starts at 0)
enum Number {
Zero,
One,
Two,
}
// enum with explicit discriminator
enum Color {
Red = 0xff0000,
Green = 0x00ff00,
Blue = 0x0000ff,
}
fn main() {
// `enums` can be cast as integers.
println!("zero is {}", Number::Zero as i32);
println!("one is {}", Number::One as i32);
println!("roses are #{:06x}", Color::Red as i32);
println!("violets are #{:06x}", Color::Blue as i32);
}
zero is 0 one is 1 roses are #ff0000 violets are #0000ff
use crate::List::*;
enum List {
Cons(u32, Box<List>),
Nil,
}
impl List {
fn new() -> List {
Nil
}
fn prepend(self, elem: u32) -> List {
Cons(elem, Box::new(self))
}
fn len(&self) -> u32 {
match *self {
Cons(_, ref tail) => 1 + tail.len(),
Nil => 0
}
}
fn stringify(&self) -> String {
match *self {
Cons(head, ref tail) => {
format!("{}, {}", head, tail.stringify())
},
Nil => {
format!("Nil")
},
}
}
}
fn main() {
// Create an empty linked list
let mut list = List::new();
// Prepend some elements
list = list.prepend(1);
list = list.prepend(2);
list = list.prepend(3);
// Show the final state of the list
println!("linked list has length: {}", list.len());
println!("{}", list.stringify());
}
linked list has length: 3 3, 2, 1, Nil
Option
енумерацијаOption
енумерацијаnull
(или nil
, none
и сл.) и све варијабле могу бити null
или non-null
. Проблем је што програмер не дефинише могућност ове вредности
кроз тип па самим тим компајлер нема могућност да провери да ли кôд исправно
обрађује ову могућност.
У презентацији из 2009 године под називом Null References: The Billion Dollar
Mistake Tony Hoare, који је први увео null
вредност као концепт је написао
следеће:
I call it my billion-dollar mistake. At that time, I was designing the first comprehensive type system for references in an object-oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
Option
је генерички enum
тип у Расту.
enum Option<T> {
None,
Some(T),
}
Примери употребе:
let some_number = Some(5); // тип је Option<i32>
let some_string = Some("a string"); // тип је Option<&str>
let absent_number: Option<i32> = None; // немогућа инференца
Option<T>
бољи од null
?let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
$ cargo run Compiling enums v0.1.0 (file:///projects/enums) error[E0277]: cannot add `Option<i8>` to `i8` --> src/main.rs:5:17 | 5 | let sum = x + y; | ^ no implementation for `i8 + Option<i8>` | = help: the trait `Add<Option<i8>>` is not implemented for `i8` For more information about this error, try `rustc --explain E0277`. error: could not compile `enums` due to previous error
Option<T>
податке?Option<T>
и могућност да вредност буде
None
.Option<T>
тип има богат API који је потребно знати јер је овај тип врло често
у употреби.let x = Some(2);
let y = None;
assert_eq!(x.or(y), Some(2));
let x = None;
let y = Some(100);
assert_eq!(x.or(y), Some(100));
match
изразmatch
израз ће извршити подударање уз исцрпљивање свих могућност. Уколико
нека могућност није обрађена компајлер ће пријавити грешку.match
као машина за сортирање новчића
За почетак можемо match
израз посматрати као аутомат за сортирање новчића.
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
match
почиње са изразом произвољног типа.=>
) која представља кôд који се
евалуира у случају подударања. Руке су раздвојене зарезима.match
израза биће вредност евалуираног кода руке чије
је подударање успелоМожемо на десној страни користити произвољан израз па и блок кода.
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
Arizona,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
match
је исцрпанfn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(UsState::Alabama) | Coin::Quarter(UsState::Alaska) => 25
}
}
$ cargo run Compiling match_test v0.1.0 (/home/igor/NTP/match_test) error[E0004]: non-exhaustive patterns: `Quarter(Arizona)` not covered --> src/main.rs:21:11 | 13 | / enum Coin { 14 | | Penny, 15 | | Nickel, 16 | | Dime, 17 | | Quarter(UsState), | | ------- not covered 18 | | } | |_- `Coin` defined here ... 21 | match coin { | ^^^^ pattern `Quarter(Arizona)` not covered | = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms = note: the matched value is of type `Coin`
Option<T>
типомfn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
Пошто је match
исцрпан не можемо заборавити да обрадимо None
случај.
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
$ cargo run Compiling enums v0.1.0 (file:///projects/enums) error[E0004]: non-exhaustive patterns: `None` not covered --> src/main.rs:3:15 | 3 | match x { | ^ pattern `None` not covered | = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms = note: the matched value is of type `Option<i32>` For more information about this error, try `rustc --explain E0004`. error: could not compile `enums` due to previous error
Употреба Option<T>
API-ја. map
метода. Више речи у поглављу о функционалном програмирању.
fn plus_one(x: Option<i32>) -> Option<i32> {
x.map(|x| x + 1)
}
fn main () {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("{five:?}, {six:?}, {none:?}")
}
Some(5), Some(6), None
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
Ако желимо да обрадимо све случајеве али нас вредност не интересује можемо
користити _
.
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn reroll() {}
fn main() {
let triple = (0, -2, 3);
// TODO ^ Try different values for `triple`
println!("Tell me about {:?}", triple);
// Match can be used to destructure a tuple
match triple {
// Destructure the second and third elements
(0, y, z) => println!("First is `0`, `y` is {:?}, and `z` is {:?}", y, z),
(1, ..) => println!("First is `1` and the rest doesn't matter"),
(.., 2) => println!("last is `2` and the rest doesn't matter"),
(3, .., 4) => println!("First is `3`, last is `4`, and the rest doesn't matter"),
// `..` can be used to ignore the rest of the tuple
_ => println!("It doesn't matter what they are"),
// `_` means don't bind the value to a variable
}
}
fn main() {
let array = [4, -2, 6];
match array {
[0, second, third] =>
println!("array[0] = 0, array[1] = {}, array[2] = {}", second, third),
[1, _, third] => println!(
"array[0] = 1, array[2] = {} and array[1] was ignored",
third
),
[-1, second, ..] => println!(
"array[0] = -1, array[1] = {} and all the other ones were ignored",
second
),
[3, second, tail @ ..] => println!(
"array[0] = 3, array[1] = {} and the other elements were {:?}",
second, tail
),
[first, middle @ .., last] => println!(
"array[0] = {}, middle = {:?}, array[2] = {}",
first, middle, last
),
}
}
array[0] = 4, middle = [-2], array[2] = 6
#[allow(dead_code)]
enum Color {
Red,
Blue,
Green,
RGB(u32, u32, u32),
HSV(u32, u32, u32),
HSL(u32, u32, u32),
CMY(u32, u32, u32),
CMYK(u32, u32, u32, u32),
}
fn main() {
let color = Color::RGB(122, 17, 40);
println!("What color is it?");
match color {
Color::Red => println!("The color is Red!"),
Color::Blue => println!("The color is Blue!"),
Color::Green => println!("The color is Green!"),
Color::RGB(r, g, b) =>
println!("Red: {}, green: {}, and blue: {}!", r, g, b),
Color::HSV(h, s, v) =>
println!("Hue: {}, saturation: {}, value: {}!", h, s, v),
Color::HSL(h, s, l) =>
println!("Hue: {}, saturation: {}, lightness: {}!", h, s, l),
Color::CMY(c, m, y) =>
println!("Cyan: {}, magenta: {}, yellow: {}!", c, m, y),
Color::CMYK(c, m, y, k) =>
println!("Cyan: {}, magenta: {}, yellow: {}, key (black): {}!", c, m, y, k),
}
}
fn main() {
let reference = &4;
match reference {
&val => println!("Got a value via destructuring: {:?}", val),
}
match *reference {
val => println!("Got a value via dereferencing: {:?}", val),
}
let _not_a_reference = 3;
let ref _is_a_reference = 3;
let value = 5;
let mut mut_value = 6;
match value {
ref r => println!("Got a reference to a value: {:?}", r),
}
match mut_value {
ref mut m => {
*m += 10;
println!("We added 10. `mut_value`: {:?}", m);
},
}
}
Got a value via destructuring: 4 Got a value via dereferencing: 4 Got a reference to a value: 5 We added 10. `mut_value`: 16
fn main() {
struct Foo {
x: (u32, u32),
y: u32,
}
let foo = Foo { x: (1, 2), y: 3 };
match foo {
Foo { x: (1, b), y } => println!("First of x is 1, b = {}, y = {} ", b, y),
// you can destructure structs and rename the variables,
// the order is not important
Foo { y: 2, x: i } => println!("y is 2, i = {:?}", i),
// and you can also ignore some variables:
Foo { y, .. } => println!("y = {}, we don't care about x", y),
// this will give an error: pattern does not mention field `x`
//Foo { y } => println!("y = {}", y),
}
}
if let
kontrola toka
if let
нам омогућава комбинацију if
и let
исказа у мање опширан исказ за обраду
само једног случаја поклапања и игнорисање осталих.
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
Ово можемо концизније исказати са:
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}
match
израза.
Обрасци за подударање могу имати две форме: поништив (refutable) и непоништив
(irrefutable). Образац који увек мора успети (нпр. let x = 5;
) је непоништив.
Образац који не мора да успе увек је поништив (нпр. if let Some(x) = a_value
неће успети за a_value==None
).
Параметри функција, let
искази и for
петље прихватају само непоништиве обрасце
јер програм не може урадити ништа смислено уколико подударање не успе.
if let
и while let
прихватају и поништиве и непоништиве обрасце али ће нас
компајлер упозорити уколико користимо непоништиве.
Можемо користити и else
грану. Следеће је еквивалентно.
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {:?}!", state),
_ => count += 1,
}
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
let else
Омогућава употребу поништивих образаца у исказима доделе. Уколико додела не
успева можемо дивергирати са else
граном (нпр. break
, return
, panic!
).
use std::str::FromStr;
fn get_count_item(s: &str) -> (u64, &str) {
let mut it = s.split(' ');
let (Some(count_str), Some(item)) = (it.next(), it.next()) else {
panic!("Can't segment count item pair: '{s}'");
};
let Ok(count) = u64::from_str(count_str) else {
panic!("Can't parse integer: '{count_str}'");
};
(count, item)
}
assert_eq!(get_count_item("3 chairs"), (3, "chairs"));
while let
Слично као if let
и while let
чини одређене конструкције са подударањем образаца
језгровитије.
На пример, ако имамо нешто попут:
// Make `optional` of type `Option<i32>`
let mut optional = Some(0);
// Repeatedly try this test.
loop {
match optional {
// If `optional` destructures, evaluate the block.
Some(i) => {
if i > 9 {
println!("Greater than 9, quit!");
optional = None;
} else {
println!("`i` is `{:?}`. Try again.", i);
optional = Some(i + 1);
}
},
// Quit the loop when the destructure fails:
_ => { break; }
}
}
То можемо написати као:
fn main() {
// Make `optional` of type `Option<i32>`
let mut optional = Some(0);
// This reads: "while `let` destructures `optional` into
// `Some(i)`, evaluate the block (`{}`). Else `break`.
while let Some(i) = optional {
if i > 9 {
println!("Greater than 9, quit!");
optional = None;
} else {
println!("`i` is `{:?}`. Try again.", i);
optional = Some(i + 1);
}
}
}
Box<T>
Box<T>
Box<T>
употребаfn main() {
let b = Box::new(5);
println!("b = {}", b);
}
b = 5
Cons
листе. Из LISP-a. Креирање листе употребом парова.
(1, (2, (3, Nil)))
enum List {
Cons(i32, List),
Nil,
}
use List::{Cons, Nil};
fn main() {
let list = Cons(1, Cons(2, Cons(3, Nil)));
}
error[E0072]: recursive type `List` has infinite size
--> src/main.rs:2:1
|
2 | enum List {
| ^^^^^^^^^
3 | Cons(i32, List),
| ---- recursive without indirection
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
3 | Cons(i32, Box<List>),
| ++++ +
For more information about this error, try `rustc --explain E0072`.
error: could not compile `cargoXtBWbE` (bin "cargoXtBWbE") due to previous error
List
типа је бесконачна. Компајлер предлаже употребу индирекције кроз Box
.enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
Величина типа је величина највеће алтернативе.
Како се рачуна величина List
типа?
enum List {
Cons(i32, List),
Nil,
}
use List::{Cons, Nil};
List
типа да користи Box<T>
enum List {
Cons(i32, Box<List>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}
Box<T>
типа је фиксна и позната у време компајлирања. Садржи само
адресу садржаја на хипу.Deref
редефинишу оператор *
.Box<T>
као референцеfn main() {
let x = 5;
let y = Box::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
--> src/main.rs:14:19
|
14 | assert_eq!(5, *y);
| ^^
For more information about this error, try `rustc --explain E0614`.
error: could not compile `cargoEj7N5h` (bin "cargoEj7N5h") due to previous error
Deref
особинеuse std::ops::Deref;
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
Target
је придружени тип (Associated type) особине Deref
.deref
враћа референцу на Target
тип.
Приликом дереференцирања, вредност није обична референца, позива се deref
уколико тип имплементира Deref
и враћена референца се затим дереференцира на
стандардни начин. Компајлер у позадини извршава ово:
*(y.deref())
deref
враћа референцу на Target
тип а не директно вредност?Јер би дошло до премештања вредности из поинтера а то не желимо да се деси. Желимо вредност само да позајмимо.
Deref
коерцијаDeref
коерција представља имплицитну конверзију референци на тип који
имплементира Deref
у референцу на његов Target
тип приликом позива функција и
метода.
На пример, тип &String
se имплицитно конвертује у &str
jer у стандардној
библиотеци постоји следећа имплементација:
impl ops::Deref for String {
type Target = str;
#[inline]
fn deref(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.vec) }
}
}
Deref
коерција типа MyBox
fn hello(name: &str) {
println!("Hello, {name}!");
}
fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&m);
}
&MyBox<String>
у &String
и затим се конвертује
&String
u &str
на основу имплементације у стандардној библиотеци.deref
ако постоји ланац конверзије који ће
довести до траженог типа.&str
уместо &String
у листи параметара функције.
Функција ће моћи да се користи и за &str
и за све друге типове који се
имплицитном коерцијом могу превести у &str
.Када Раст не би имплементирао имплицитну коерцију морали бисмо ово да пишемо:
fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&(*m)[..]);
}
Drop
особинеDrop
је друга особина битна за паметне поинтере.Drop
тада ће компајлер позвати drop
методу приликом
изласка вредности из опсега важења.Box<T>
имплементира Drop
да би деалоцирао хип меморију приликом
изласка из опсега важења.struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created.");
}
CustomSmartPointers created. Dropping CustomSmartPointer with data `other stuff`! Dropping CustomSmartPointer with data `my stuff`!
Rc<T>
enum List {
Cons(i32, Box<List>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
let b = Cons(3, Box::new(a));
let c = Cons(4, Box::new(a));
}
error[E0382]: use of moved value: `a`
--> src/main.rs:12:30
|
10 | let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
| - move occurs because `a` has type `List`, which does not implement the `Copy` trait
11 | let b = Cons(3, Box::new(a));
| - value moved here
12 | let c = Cons(4, Box::new(a));
| ^ value used here after move
For more information about this error, try `rustc --explain E0382`.
error: could not compile `cargo1mCqK5` (bin "cargo1mCqK5") due to previous error
Rc<T>
паметног поинтераRc<T>
enum List {
Cons(i32, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
}
Rc::clone
не клонира вредност већ само увећава бројач.a.clone()
али је конвенција да се пише Rc::clone(&a)
јер
овде не долази заиста до клонирања па је лакше уочити оваква места у коду.enum List {
Cons(i32, Rc<List>),
Nil,
}
use std::rc::Rc;
use crate::List::{Cons, Nil};
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("count after creating b = {}", Rc::strong_count(&a));
{
let c = Cons(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a));
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
count after creating a = 1 count after creating b = 2 count after creating c = 3 count after c goes out of scope = 2
Rc<T>
није сигуран у контексту нити (not threadsafe). Тип који јесте је
Arc<T>
- atomic reference counting.Rc<T>
.Vec<T>
)let v: Vec<i32> = Vec::new();
Ако креирамо вектор са већ постојећим елементима чешће користимо vec!
макро:
let v = vec![1, 2, 3];
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
get
методе.let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
println!("The third element is {third}");
let third: Option<&i32> = v.get(2);
match third {
Some(third) => println!("The third element is {third}"),
None => println!("There is no third element."),
}
get
метода увек успева и враћа Option<&T>
.let v = vec![1, 2, 3, 4, 5];
let does_not_exist = &v[100];
let does_not_exist = v.get(100);
get
би вратио None
.let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("The first element is: {first}");
$ cargo run Compiling collections v0.1.0 (file:///projects/collections) error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable --> src/main.rs:6:5 | 4 | let first = &v[0]; | - immutable borrow occurs here 5 | 6 | v.push(6); | ^^^^^^^^^ mutable borrow occurs here 7 | 8 | println!("The first element is: {first}"); | ----- immutable borrow later used here For more information about this error, try `rustc --explain E0502`. error: could not compile `collections` due to previous error
Имамо референцу на први елемент вектора а покушавамо промењивом референцом да додамо на крај. Ово делује као да би било безбедно.
Зашто Раст не дозвољава овакве операције?
Приликом додавања новог елемента може доћи до попуњавања текуће алоциране зоне и алоцирања нове зоне уз премештање свих елемената на нову локацију чиме би све текуће референце постале невалидне. Правило позајмљивања спречава овакве грешке.
let v = vec![100, 32, 57];
for i in &v {
println!("{i}");
}
100 32 57
Пошто итерирамо преко позајмљене вредности &v
, тип од i
ће бити позајмљен
(&u32
). Уколико би итерирали преко поседоване вредности for i in v
тада би сви
елементи из вектора били премештени (тј. конзумирани) од стране for
петље и не
би могли више да им приступимо.
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}
println!("{v:?}");
[150, 82, 107]
#[derive(Debug)]
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
println!("{row:?}");
[Int(3), Text("blue"), Float(10.12)]
match
изразом.{
let v = vec![1, 2, 3, 4];
// do stuff with v
} // <- v goes out of scope and is freed here
String
str
који
се обично користи у позајмљеној форми &str
- стринг исечак који представља
референцу на UTF-8 енкодован садржај.String
, дефинисан у Раст стандардној библиотеци, је растући, промењиви,
поседовани UTF-8 енкодовани стринг тип.String
вредност се креира на следећи начин:let mut s = String::new();
Обично користимо to_string
методу типа који имплементира Display
особину да би
добили почетну вреност стринга.
let data = "initial contents";
let s = data.to_string();
// the method also works on a literal directly:
let s = "initial contents".to_string();
Такође, можемо користити String::from
:
let s = String::from("initial contents");
let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");
let mut s = String::from("foo");
s.push_str("bar");
let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(s2);
println!("s2 is {s2}");
s2
је позајмљено и може да се користи после додавања.
Додавање једног карактера:
let mut s = String::from("lo");
s.push('l');
+
оператораlet s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
+
оператор конзумира први операнд тако да у претходном примеру s1
варијабла не
важи после задње линије.
+
оператор интерно користи add
методу чија сигнатура је нешто попут:
fn add(self, s: &str) -> String {
Обратите пажњу да је други параметар типа &str
док је прослеђен тип &String
.
Компајлер ово прихвата. Зашто?
Користи се техника Deref coercion, јер String
имплементира особину Deref<Target=str>
.
format!
макроаУместо:
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = s1 + "-" + &s2 + "-" + &s3;
Можемо писати:
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{s1}-{s2}-{s3}");
let s1 = String::from("hello");
let h = s1[0];
$ cargo run Compiling collections v0.1.0 (file:///projects/collections) error[E0277]: the type `String` cannot be indexed by `{integer}` --> src/main.rs:3:13 | 3 | let h = s1[0]; | ^^^^^ `String` cannot be indexed by `{integer}` | = help: the trait `Index<{integer}>` is not implemented for `String` = help: the following other types implement trait `Index<Idx>`: <String as Index<RangeFrom<usize>>> <String as Index<RangeFull>> <String as Index<RangeInclusive<usize>>> <String as Index<RangeTo<usize>>> <String as Index<RangeToInclusive<usize>>> <String as Index<std::ops::Range<usize>>> For more information about this error, try `rustc --explain E0277`. error: could not compile `collections` due to previous error
Зашто?
let hello = String::from("Hola");
println!("{}", hello.len());
4
let hello = String::from("Здраво");
println!("{}", hello.len()); // Дужина није 6 већ 12!
12
Шта би требао да врати елемент на одређеном индексу?
Три начина посматрања стринга: низ бајтова, низ скалара и низ графем кластера.
На пример, хинди реч "“नम�ते" ће у меморији бити следећи низ бајтова:
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]
Ако исту реч посматрамо као низ скалара или карактера (Раст char
тип) онда имамо
следеће вредности:
['न', 'म', 'स', '◌्', 'त', '◌े ']
Четврти и шести карактер су дијакритици тј. немају смисла сами за себе.
Ако посматрамо исти садржај као графем кластере (најприближније концепту слова) онда видимо следеће:
["न", "म", "स्", "ते"]
Додатни разлог зашто не треба дозволити индексни приступ код стрингова јесте
чињеница да индексни приступ увак има сложеност O(1)
што се код стрингова не
може гарантовати.
Могуће је креирање исечака стрингова с тим да је на програмеру да обезбеди да се
границе исечака налазе на крајевима char
типа тј. није могуће да играница исечка
буде унутар карактера.
На пример, ово је могуће:
let hello = "Здраво";
let s = &hello[0..4];
println!("{s}")
Зд
Али ово је грешка:
let hello = "Здраво";
let s = &hello[0..3];
println!("{s}")
thread 'main' panicked at 'byte index 3 is not a char boundary; it is inside 'д' (bytes 2..4) of `Здраво`', src/main.rs:4:10
Начин да обрадимо све елементе стринга је да итерирамо, при чему имамо две методе:
chars
- ако желимо да обрадимо стринг као низ карактера
for c in "Зд".chars() {
println!("{c}");
}
bytes
- ако желимо да обрадимо стринг као низ бајтова
for b in "Зд".bytes() {
println!("{b}");
}
HashMap<K, V>
)use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name).copied().unwrap_or(0);
get
метода враћа тип Option<&T>
, дакле None
вредност ако за дати кључ не
постоји везана вредност. Позивамо copied
да добијемо Option<T>
са копираном
вредношћу.use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (key, value) in &scores {
println!("{key}: {value}");
}
Yellow: 50 Blue: 10
Copy
особину биће копиран у мапу, у супротном биће
премештен и хеш мапа постаје власник.use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
// field_name and field_value are invalid at this point, try using them and
// see what compiler error you get!
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores);
{"Blue": 25}
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores);
{"Yellow": 50, "Blue": 10}
Користимо Entry API
.
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map);
{"wonderful": 1, "hello": 1, "world": 2}
panic!
fn main() {
panic!("crash and burn");
}
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished dev [unoptimized + debuginfo] target(s) in 0.25s
Running `target/debug/panic`
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
panic!
повратни траг (backtrace)fn main() {
let v = vec![1, 2, 3];
v[99];
}
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished dev [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
stack backtrace:
0: rust_begin_unwind
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/panicking.rs:584:5
1: core::panicking::panic_fmt
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:142:14
2: core::panicking::panic_bounds_check
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:84:5
3: <usize as core::slice::index::SliceIndex<[T]>>::index
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:242:10
4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:18:9
5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/alloc/src/vec/mod.rs:2591:9
6: panic::main
at ./src/main.rs:4:5
7: core::ops::function::FnOnce::call_once
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/ops/function.rs:248:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
У програмском језику Ц, читање ван опсега је недефинисано понашање и компајлер може да произведе кôд који ради произвољну ствар. Најчешће ће да чита са локације која се налази иза краја низа што представља грешку buffer overread која доводи до сигурносних пропуста.
Result<T,E>
Result<T,E>
:enum Result<T, E> {
Ok(T),
Err(E),
}
Фајл: src/main.rs
:
use std::fs::File;
fn main() {
let greeting_file_result = File::open("hello.txt");
}
greeting_file_result
ће бити типа Result<std::fs::File,
std::io::Error>
. Уколико је враћена варијанта Ok
она ће у себи садржати фајл,
уколико се врати варијанта Err
, у њој ће бити грешка типа std::io::Error
.
Фајл: src/main.rs
:
use std::fs::File;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error);
}
},
};
}
Result
типа:use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Problem creating the file: {:?}", error);
})
} else {
panic!("Problem opening the file: {:?}", error);
}
});
}
unwrap
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt").unwrap();
}
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:4:49
expect
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt")
.expect("hello.txt should be included in this project");
}
thread 'main' panicked at 'hello.txt should be included in this project: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:5:10
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let username_file_result = File::open("hello.txt");
let mut username_file = match username_file_result {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut username = String::new();
match username_file.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(e) => Err(e),
}
}
Сада позиваоц може да одлучи како да обради грешку. Нпр. може да:
panic!
?
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut username_file = File::open("hello.txt")?;
let mut username = String::new();
username_file.read_to_string(&mut username)?;
Ok(username)
}
?
елиминише тзв. boilerplate код.
Постоји значајна разлика између ?
оператора и match
израза. ?
оператор ће
аутоматски радити конверзију грешака за све грешке које имплементирају From
особину. На пример, ако нека наша грешка OurError
имплементира конверзију из
std::io::Error
тако што имамо impl From<std::io::Error> for OurError
тада можемо
декларисати да наша функција враћа OurError
и даље користити пречицу ?
на
местима која резултују са std::io::Error
.
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut username = String::new();
File::open("hello.txt")?.read_to_string(&mut username)?;
Ok(username)
}
?
оператора код Option
типаfn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}
?
оператора у функцији main
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}
use
main
која представља улазну тачку.$ cargo new my-project
Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs
Cargo.toml
конфигурациони фајл.src/main.rs
, док је корен
библиотечког сандука src/lib.rs
.src/bin
.backyard
├── Cargo.lock
├── Cargo.toml <- конфигурација пакета
└── src
├── garden
│ └── vegetables.rs <- модул
├── garden.rs <- модул
└── main.rs <- корен бинарног сандука
cargo new restaurant --lib
Фајл: src/lib.rs
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
Модули могу имати друге модуле, структуре, енумерације, константе, особине итд.
Модули се повезују и чине структуру типа стабла чији корен је корен сандука
(main.rs
ili lib.rs
).
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
Аналогија је систем фајлова и фолдера.
Елементи Раст кода се могу референцирати путањама које представљају синтаксне елементе за навигацију над стаблом модула.
Постоје две врсте путања:
crate
.self
i super
.mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
error[E0603]: module `hosting` is private
--> src/lib.rs:12:21
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` due to 2 previous errors
Ако учинимо модул hosting
јавним:
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
И даље имамо грешку:
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:9:37
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:12:30
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` due to 2 previous errors
Морамо учинити и функцију add_to_waitlist
јавном јер иако је модул јаван његови
елементи су подразумевано приватни.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
Кôд који је јавно доступан ван библиотечког сандука чини јавни API библиотеке и представља "уговор" са корисницима библиотеке. Стога је важно поштовати одређена правила измене ових елемената.
Делови кода који нису споља јавно доступни могу слободно да се мењају јер неће утицати на кориснике.
super
super
се користи у ситуацији када релативну путању креирамо са почетком у
родитељском модулу.fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
pub
кључне речи када су у питању
структуре и енумерације.mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
let mut meal = back_of_house::Breakfast::summer("Rye");
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// Следећа линија се не компајлира јер је поље приватно
// meal.seasonal_fruit = String::from("blueberries");
}
Са друге стране, варијанте енумерација су увек јавне:
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
use
use
можемо увести елемент у текући опсег.mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
Аналогно креирању пречица (shortcuts) у фајл систему.
use
само креира пречицу
Следећи пример се не компајлира јер смо померили функцију eat_at_restaurant
у
модул customer
и релативна пречица више није валидна.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
mod customer {
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
}
HashMap
из стандардне библиотеке:use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
as
за промену именаРаст не дозвољава да постоје различити елементи у опсегу са истим именом. Уколико желимо да уведемо елементе са истим називом можемо:
Увести родитељски модул, као у следећем примеру:
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
}
fn function2() -> io::Result<()> {
// --snip--
}
Променити име у текућем опсегу употребом кључне речи as
.
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
pub use
use
он је приватан за приступ споља.pub use
.mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
Фајл Cargo.toml
:
...
[dependencies]
rand = "0.8.5"
...
И затим можемо увести елементе из rand
сандука са:
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
}
Пакети се подразумевано преузимају са crates.io
. std
библиотека је сандук, увек
је локално присутан, није потребно посебно га наводити у конфигурацији али је
елементе које користимо потребно увести.
use std::collections::HashMap;
На пример:
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
Можемо писати као:
// --snip--
use std::{cmp::Ordering, io};
// --snip--
Moжемо истовремено увести и модул и неке од његових елемената. На пример, уместо:
use std::io;
use std::io::Write;
Можемо писати:
use std::io::{self, Write};
Ако желимо да уведемо све елементе неког модула можемо писати:
use std::collections::*;
Модуле често пишемо у посебним фајловима због лакшег приступа и организације.
Фајл src/lib.rs
:
pub mod front_of_house; // <- наводимо име фајла модула
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
А затим у фајлу src/front_of_house.rs
pub mod hosting {
pub fn add_to_waitlist() {}
}
А можемо даље декомпоновати и извући модул hosting
из фајла front_of_house.rs
:
Фајл src/front_of_house.rs
:
pub mod hosting;
Фајл src/front_of_house/hosting.rs
:
pub fn add_to_waitlist() {}
mod
је потребно навести само једном. Тиме се модул увезује у стабло модула и
могуће га је референцирати путањама из остатка кода. Односно, mod
није исто што
и include
из других програмских језика.
Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence.
– Edsger W. Dijkstra, “The Humble Programmer”, 1972
$ cargo new adder --lib
Created library `adder` project
$ cd adder
Генерисан тест ће бити:
Фајл src/lib.rs
:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.57s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn larger_can_hold_smaller() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(larger.can_hold(&smaller));
}
}
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
running 1 test
test tests::larger_can_hold_smaller ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests rectangle
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn larger_can_hold_smaller() {
// --snip--
}
#[test]
fn smaller_cannot_hold_larger() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(!smaller.can_hold(&larger));
}
}
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
running 2 tests
test tests::larger_can_hold_smaller ... ok
test tests::smaller_cannot_hold_larger ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests rectangle
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
// --snip--
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width < other.width && self.height > other.height
}
}
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
running 2 tests
test tests::larger_can_hold_smaller ... FAILED
test tests::smaller_cannot_hold_larger ... ok
failures:
---- tests::larger_can_hold_smaller stdout ----
thread 'tests::larger_can_hold_smaller' panicked at 'assertion failed: larger.can_hold(&smaller)', src/lib.rs:28:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::larger_can_hold_smaller
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
assert_eq!
и assert_ne!
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_adds_two() {
assert_eq!(4, add_two(2));
}
}
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.58s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::it_adds_two ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Уколико тест не успе:
pub fn add_two(a: i32) -> i32 {
a + 3
}
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::it_adds_two ... FAILED
failures:
---- tests::it_adds_two stdout ----
thread 'tests::it_adds_two' panicked at 'assertion failed: `(left == right)`
left: `4`,
right: `5`', src/lib.rs:11:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::it_adds_two
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
#[test]
fn greeting_contains_name() {
let result = greeting("Carol");
assert!(
result.contains("Carol"),
"Greeting did not contain name, value was `{}`",
result
);
}
$ cargo test
Compiling greeter v0.1.0 (file:///projects/greeter)
Finished test [unoptimized + debuginfo] target(s) in 0.93s
Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
running 1 test
test tests::greeting_contains_name ... FAILED
failures:
---- tests::greeting_contains_name stdout ----
thread 'tests::greeting_contains_name'
panicked at 'Greeting did not contain name, value was `Hello!`', src/lib.rs:12:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::greeting_contains_name
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out;
finished in 0.00s
error: test failed, to rerun pass `--lib`
should_panic
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!(
"Guess value must be greater than or equal to 1, got {}.",
value
);
} else if value > 100 {
panic!(
"Guess value must be less than or equal to 100, got {}.",
value
);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "less than or equal to 100")]
fn greater_than_100() {
Guess::new(200);
}
}
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_two_and_two() {
assert_eq!(4, add_two(2));
}
#[test]
fn add_three_and_two() {
assert_eq!(5, add_two(3));
}
#[test]
fn one_hundred() {
assert_eq!(102, add_two(100));
}
}
$ cargo test one_hundred
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.69s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::one_hundred ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
$ cargo test add
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 2 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
Option<T>
).fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = number_list[0];
for number in number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {}", largest);
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = number_list[0];
for number in number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {}", largest);
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let mut largest = number_list[0];
for number in number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {}", largest);
}
Дуплирање кода!
Елиминишемо дуплирање увођењем функције.
fn largest(list: &[i32]) -> i32 {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let result = largest(&number_list);
println!("The largest number is {}", result);
}
Поступак елиминације дуплог кода је подразумевао следеће:
Имамо функцију за проналажење највећег елемента у листи са различитим типовима.
fn largest_i32(list: &[i32]) -> i32 {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn largest_char(list: &[char]) -> char {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest_i32(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest_char(&char_list);
println!("The largest char is {}", result);
}
Функције су готово идентичне. Разлика је само у типу. Пишемо генеричку функцију
тако што дефинишемо генерички тип унутар <>
после назива функције:
fn largest<T>(list: &[T]) -> T {
T
је генерички тип и у време компајлирања биће замењен са конкретним типом.
Сада је наш код следећи:
fn largest<T>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
Али се не компајлира.
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0369]: binary operation `>` cannot be applied to type `T`
--> src/main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- T
| |
| T
|
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
| ++++++++++++++++++++++
For more information about this error, try `rustc --explain E0369`.
error: could not compile `chapter10` due to previous error
Тип T
може бити било који тип па и тип који не дефинише операцију >
која се
користи у коду. Дакле, морамо ограничити који типови су могући.
Видећемо како се ово ради у наставку у причи о особинама (Traits).
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}
Типови оба поља морају бити исти.
struct Point<T> {
x: T,
y: T,
}
fn main() {
let wont_work = Point { x: 5, y: 4.0 };
}
$ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0308]: mismatched types --> src/main.rs:7:38 | 7 | let wont_work = Point { x: 5, y: 4.0 }; | ^^^ expected integer, found floating-point number For more information about this error, try `rustc --explain E0308`. error: could not compile `chapter10` due to previous error
Ако желимо да поља имају различите типове онда морамо имати различите генеричке типове.
struct Point<T, U> {
x: T,
y: U,
}
fn main() {
let both_integer = Point { x: 5, y: 10 };
let both_float = Point { x: 1.0, y: 4.0 };
let integer_and_float = Point { x: 5, y: 4.0 };
}
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
}
Пошто смо навели <T>
иза кључне речи impl
Раст даље зна да је тип T
генерички
а не конкретни тип тако да је метода x
за Point<T>
дефинисана над свим типовима
T
.
Могли смо нпр. методу дефинисати само за одређени Point
тип.
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
Овај impl
блок дефинише методу distance_from_origin
али само за Point<f32>
док
остали Point
типови неће имати ову методу.
impl
блоковима
Генерички типови у структурама и методама не морају бити исти. На пример, можемо
креирати методу mixup
која узима две инстанце Point
и враћа нови Point
тип где
ће прво поље имати исти тип као прва тачка а друго поље као друга тачка.
struct Point<X1, Y1> {
x: X1,
y: Y1,
}
impl<X1, Y1> Point<X1, Y1> {
fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c' };
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
p3.x = 5, p3.y = c
Генерички типови X2
и Y2
су наведени само у методи јер су релевантни само у
њеном контексту.
Употреба генеричког кода не доводи до деградације перформанси. Раст користи технику мономорфизације (Monomorphization) код које ће компајлер инстанцирати конкретан кôд за сваки тип посебно.
То би значило следеће. Ако имамо код:
let integer = Some(5);
let float = Some(5.0)
Компајлер ће произвести код еквивалентан следећем:
enum Option_i32 {
Some(i32),
None,
}
enum Option_f64 {
Some(f64),
None,
}
fn main() {
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
}
NewsArticle
или Tweet
инстанци.Summary
особину да опишемо ову функционалност.pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
Методе особина се позивају као и обичне методе. Једина разлика је у томе што
методе особина морају бити доступне у опсегу (укључити их са use
).
use aggregator::{Summary, Tweet};
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}
Ограничење у употреби особина је да можемо имплементирати особину над типом једино ако је бар једно од њих локално за наш сандук. Ово правило је део скупа правила који се називају coherence. Конкретно ово правило зове се orphan rule и спречава некомпатибилне имплементације особина над типовима од стране више сандука.
Без овог правила могло би се десити да два различита сандука имплементирају исту особину над истим типом на различите начине и компајлер не би знао коју верзију да користи.
Особина може имати подразумевану имплементацију метода.
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
Да би користили подразумевану имплементацију можемо навести празно тело у impl
блоку:
impl Summary for NewsArticle {}
И затим можемо позвати summarize
методу:
let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
};
println!("New article available! {}", article.summarize());
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
Сада је потребно и довољно да приликом имплементације дефинишемо
summarize_author
методу:
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
Функција notify
ради над свим типовима који имплементирају Summary
.
Претходна употреба impl
код параметра је синтаксни шећер за општи облик
навођења ограничења кроз особине.
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
Ограничења кроз особине (Trait bounds) стављамо после двотачке код навођења генеричког типа. Компајлер ће верификовати да све што користимо над вредностима овог типа унутар функције је заиста дефинисано особинама наведеним у заглављу.
impl Trait
синтаксе је концизнија код једноставних примера али је пуна синтакса
боља код сложенијих примера.
На пример:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
би у пуној синтакси било:
pub fn notify<T: Summary>(item1: &T, item2: &T) {
Можемо дефинисати и више ограничења употребом +
синтаксе. На пример, ако
параметар мора да имплементира Summary
и Display
:
pub fn notify(item: &(impl Summary + Display)) {
или у пуној синтакси:
pub fn notify<T: Summary + Display>(item: &T) {
where
клаузула у ограничењимаfn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
where
клаузула измешта дефинисање ограничења после заглавља функције чиме
се постиже боља читкост:fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{
impl Trait
као повратна вредност функцијеfn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
Посебно значајно у контексту затворења (closures) и итератора где је конкретан тип превише сложен за писање или је познат само компајлеру.
impl Trait
синтакса за повратне вредности је могућа само ако функција враћа
један тип који имплементира дату особину. На пример, ова функција неће радити
ако функција може вратити NewsArticle
или Tweet
.
largest
генеричке функције
На претходним сладовима имали смо функцију largest
. Сада можемо да завршимо
њену дефиницију.
Грешка је била следећа:
$ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0369]: binary operation `>` cannot be applied to type `T` --> src/main.rs:5:17 | 5 | if item > largest { | ---- ^ ------- T | | | T | help: consider restricting type parameter `T` | 1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T { | ++++++++++++++++++++++ For more information about this error, try `rustc --explain E0369`. error: could not compile `chapter10` due to previous error
Дакле, морамо ограничити типове на оне који подржавају поређење. Ова особина се
у стандардној библиотеци зове std::cmp::PartialOrd
, па ћемо преправити заглавље
функције на следећи начин:
fn largest<T: PartialOrd>(list: &[T]) -> T {
Међутим, сада имамо још једну грешку:
$ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0508]: cannot move out of type `[T]`, a non-copy slice --> src/main.rs:2:23 | 2 | let mut largest = list[0]; | ^^^^^^^ | | | cannot move out of here | move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait | help: consider borrowing here: `&list[0]` error[E0507]: cannot move out of a shared reference --> src/main.rs:4:18 | 4 | for &item in list { | ----- ^^^^ | || | |data moved here | |move occurs because `item` has type `T`, which does not implement the `Copy` trait | help: consider removing the `&`: `item` Some errors have detailed explanations: E0507, E0508. For more information about an error, try `rustc --explain E0507`. error: could not compile `chapter10` due to 2 previous errors
Уводимо додатно ограничење да генерички тип мора бити и Copy
. Тако да је пуно
решење следеће:
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
Ако желимо да имплементирамо методу само над типовима који имплементирају одређене особине.
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
{
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0597]: `x` does not live long enough --> src/main.rs:7:17 | 7 | r = &x; | ^^ borrowed value does not live long enough 8 | } | - `x` dropped here while still borrowed 9 | 10 | println!("r: {}", r); | - borrow later used here For more information about this error, try `rustc --explain E0597`. error: could not compile `chapter10` due to previous error
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
r
референцира вредност чији животни век ('b
) је краћи
од животног века варијабле r
('a
). Уколико би се ово дозволило, r
би у једном
делу референцирала невалидну вредност.
Варијабле имају животни век који је једнак синтаксном опсегу у коме је
варијабла дефинисана. Животни век имају и референце које позајмљују вредност и
смештају се у варијабле (нпр. r=&x
из претходног примера).
Једно од правила је да референца на варијаблу (тј. вредност) не може да живи дуже од саме вредности односно варијабле која је власник те вредности. Односно животни век вредности мора да садржи животни век референце позајмљене од те вредности.
{
let r;
{
let x = 5;
r = &x; // -+- &x не може да живи дуже од
} // -+ овог животног века
println!("r: {}", r);
}
Ако сместимо референцу у варијаблу, референца мора бити исправна за цео животни век варијабле у коју је смештена.
Кажемо да животни век референце мора да садржи животни век варијабле у коју је смештена.
{
let r;
{
let x = 5;
r = &x; // -+- животни век било чега што
} // | се смести у r мора да живи
println!("r: {}", r); // -+ бар оволико
}
Видимо да ово правило није задовољено. &x
не живи довољно дуго јер x
не живи
довољно дуго.
Ако претходна два правила објединимо можемо рећи да:
Животни век вредности мора да садржи животни век свих варијабли које вредност позајмљују.
Односно, варијабла која је власник вредности може отићи из опсега тек када не постоји више ни једна варијабла која чува позајмљену вредност.
Следећи код је прихваћен. Референца r
не живи дуже од податка који референцира
(x
).
{
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a |
// | |
println!("r: {}", r); // | |
// --+ |
} // ----------+
longest
која враћа дужи од два прослеђена стринга.
Прослеђујемо референцу на стринг исечак &str
.fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
Овај програм није прихваћен.
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
$ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0106]: missing lifetime specifier --> src/main.rs:9:33 | 9 | fn longest(x: &str, y: &str) -> &str { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y` help: consider introducing a named lifetime parameter | 9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { | ++++ ++ ++ ++ For more information about this error, try `rustc --explain E0106`. error: could not compile `chapter10` due to previous error
x
и y
са
повратном вредношћу која је такође референца.x
или y
или од неке глобалне вредности.'a
. Иза апострофа се пише име анотације које је најчешће
једно мало слово са почетка алфабета ('a, 'b,...
).&i32 // референца
&'a i32 // референца са експлицитним животним веком
&'a mut i32 // промењива референца са експлицитним животним веком
lt
која враћа животни век варијабле/вредности и релацију
између животних векова 'a≥'b
односно 'b≤'a
која значи да 'a
обухвата 'b
односно 'b
је садржано у 'a
.longest
је исправна јер референца која се враћа
може бити x
или y
у зависности од дужине стринга. Стога је потребно да имамо
строжији услов да је животни век повратне вредности у вези са животним веком
оба параметра.fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
'a
, функција прима две
референце чији животни век вредности обухвата 'a
и враћа референцу која мора
бити валидна бар колико и животни век 'a
тј. животни век повратне референце
мора да обухвати 'a
. Уколико такав животни век постоји код се прихвата.
Посматрајмо код који позива функцију longest
:
fn main() {
let string1 = String::from("abcd");
{
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
}
println!("The longest string is {}", result);
}
За код мора да постоји 'a
тако да важи:
lt(string1) ≥ 'a # животни век вредности првог параметара садржи 'a lt(string2) ≥ 'a # животни век вредности другог параметара садржи 'a 'a ≥ lt(result) # 'a мора да садржи животни веку result варијабле
Што не може бити испуњено истовремено, тј. не постоји 'a
које испуњава ова
ограничења.
Други начин размишљања је да повратна вредност функције позајмљује од вредности улазних параметара означених истим именом животног века.
fn main() {
let string1 = String::from("abcd");
{
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
}
println!("The longest string is {}", result);
}
Што значи да result
позајмљује од string1
и string2
и та позајмица траје све
време живота варијабле result
што не може бити валидно јер result
живи дуже
од вредности string2
.
Иако, можемо видети да ће, због дужине стринга, враћена вредност бити string1
и
програм је валидан, компајлер ово не може да закључи.
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}
fn longest<'a>(x: &str, y: &str) -> &'a str {
let result = String::from("really long string");
result.as_str()
}
$ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0515]: cannot return reference to local variable `result` --> src/main.rs:11:5 | 11 | result.as_str() | ^^^^^^^^^^^^^^^ returns a reference to data owned by the current function For more information about this error, try `rustc --explain E0515`. error: could not compile `chapter10` due to previous error
Немогуће је вратити референцу на локалну промењиву јер би то била висећа референца. Решење у овом случају је да вратимо поседовану вредност.
Дакле, синтакса за анотацију животних векова доводи у везу животне векове улазних параметара и повратних вредности. Наводи се само код референци.
На основу анотације, Раст је у стању да обави провере и обезбеди операције које су меморијски сигурне и забрани операције које би могле да доведу до нарушавања меморијске сигурности.
Анотације животног века истовремено представљају и корисне информације програмерима јер је лако видети у каквој су вези улазни параметри и излазне вредности.
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let i = ImportantExcerpt {
part: first_sentence,
};
}
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn foo<'a>(x: &'a i32)
fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
итд.
fn foo<'a>(x: &'a i32) -> &'a i32
&self
ili
&mut self
тада се овај животни век додељује свим излазним референцема.fn first_word(s: &str) -> &str {
Правило 1.
fn first_word<'a>(s: &'a str) -> &str {
Правило 2.
fn first_word<'a>(s: &'a str) -> &'a str {
fn longest(x: &str, y: &str) -> &str {
Правило 1.
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
static
'static
означава животни век
целокупног трајања извршавања програма.let s: &'static str = "I have a static lifetime.";
use std::fmt::Display;
fn longest_with_an_announcement<'a, T>(
x: &'a str,
y: &'a str,
ann: T,
) -> &'a str
where
T: Display,
{
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
RefCell<T>
RefCell<T>
типа.RefCell<T>
Rc<T>
, RefCell<T>
може имати само једног власника.Box<T>
?Подсетимо се правила позајмљивања:
Box<T>
правила позајмљивања се проверавају у време компајлирања (статички).
Ако прекршимо правила кôд се неће компајлирати.RefCell<T>
правила позајмљивања се проверавају у време извршавања.
Ако прекршимо правила програм ће прекинути са извршавањем (panic).RefCell<T>
користимо у ситуацијама када знамо да је наш програм валидан али
Раст компајлер то није у стању да верификује статички.Rc<T>
омогућава више власника. Box<T>
и RefCell<T>
само једног.Box<T>
омогућава непромењиве или промењиве позајмице које се проверавају у
време компајлирања. Rc<T>
дозвољава само непромењиве позајмице у време
компајлирања. RefCell<T>
дозвољава непромењиве и промењиве позајмице у време
извршавања.RefCell<T>
омогућава промењиве референце које се проверавају у време
извршавања, можемо мењати унутрашње податке иако имамо дељену референцу у
време компајлирања. Због тога се овај образац назива "унутрашња промењивост"
(İnterior Mutability).RefCell<T>
- Mock објектиКреирамо библиотеку за праћење потрошње неког ресурса и обавештавање када се приближавамо лимиту потрошње.
pub trait Messenger {
fn send(&self, msg: &str);
}
pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}
impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger
.send("Warning: You've used up over 75% of your quota!");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockMessenger {
sent_messages: Vec<String>,
}
impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
sent_messages: vec![],
}
}
}
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
self.sent_messages.push(String::from(message));
}
}
#[test]
fn it_sends_an_over_75_percent_warning_message() {
let mock_messenger = MockMessenger::new();
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
limit_tracker.set_value(80);
assert_eq!(mock_messenger.sent_messages.len(), 1);
}
}
$ cargo test Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker) error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference --> src/lib.rs:58:13 | 2 | fn send(&self, msg: &str); | ----- help: consider changing that to be a mutable reference: `&mut self` ... 58 | self.sent_messages.push(String::from(message)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable For more information about this error, try `rustc --explain E0596`. error: could not compile `limit-tracker` due to previous error warning: build failed, waiting for other jobs to finish...
RefCell<T>
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
struct MockMessenger {
sent_messages: RefCell<Vec<String>>,
}
impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
sent_messages: RefCell::new(vec![]),
}
}
}
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
self.sent_messages.borrow_mut().push(String::from(message));
}
}
#[test]
fn it_sends_an_over_75_percent_warning_message() {
// --snip--
assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
}
}
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
let mut one_borrow = self.sent_messages.borrow_mut();
let mut two_borrow = self.sent_messages.borrow_mut();
one_borrow.push(String::from(message));
two_borrow.push(String::from(message));
}
}
$ cargo test Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker) Finished test [unoptimized + debuginfo] target(s) in 0.91s Running unittests src/lib.rs (target/debug/deps/limit_tracker-e599811fa246dbde) running 1 test test tests::it_sends_an_over_75_percent_warning_message ... FAILED failures: ---- tests::it_sends_an_over_75_percent_warning_message stdout ---- thread 'tests::it_sends_an_over_75_percent_warning_message' panicked at 'already borrowed: BorrowMutError', src/lib.rs:60:53 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::it_sends_an_over_75_percent_warning_message test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib`
Rc<T>
+ RefCell<T>
Rc<T>
и RefCell<T>
типове.#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let value = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));
*value.borrow_mut() += 10;
println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}
$ cargo run Compiling cons-list v0.1.0 (file:///projects/cons-list) Finished dev [unoptimized + debuginfo] target(s) in 0.63s Running `target/debug/cons-list` a after = Cons(RefCell { value: 15 }, Nil) b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil)) c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))
Rc<T>
и RefCell<T>
може да доведе до цурења меморије (memory leak).use List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match self {
Cons(_, item) => Some(item),
Nil => None,
}
}
}
<<cycle-refs-header>> // <- Занемарите овy линију
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("a initial rc count = {}", Rc::strong_count(&a));
println!("a next item = {:?}", a.tail());
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("a rc count after b creation = {}", Rc::strong_count(&a));
println!("b initial rc count = {}", Rc::strong_count(&b));
println!("b next item = {:?}", b.tail());
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::clone(&b);
}
println!("b rc count after changing a = {}", Rc::strong_count(&b));
println!("a rc count after changing a = {}", Rc::strong_count(&a));
// Uncomment the next line to see that we have a cycle;
// it will overflow the stack
// println!("a next item = {:?}", a.tail());
}
: a initial rc count = 1 : a next item = Some(RefCell { value: Nil }) : a rc count after b creation = 2 : b initial rc count = 1 : b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) }) : b rc count after changing a = 2 : a rc count after changing a = 2
Rc<T>
у Weak<T>
.#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {
Red,
Blue,
}
struct Inventory {
shirts: Vec<ShirtColor>,
}
impl Inventory {
fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
user_preference.unwrap_or_else(|| self.most_stocked()) // <- Затворење
}
fn most_stocked(&self) -> ShirtColor {
let mut num_red = 0;
let mut num_blue = 0;
for color in &self.shirts {
match color {
ShirtColor::Red => num_red += 1,
ShirtColor::Blue => num_blue += 1,
}
}
if num_red > num_blue {
ShirtColor::Red
} else {
ShirtColor::Blue
}
}
}
<<giveaway-types>>
<<giveaway-inventory>> // <- Занемарите ове линије
fn main() {
let store = Inventory {
shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
};
let user_pref1 = Some(ShirtColor::Red);
let giveaway1 = store.giveaway(user_pref1);
println!(
"The user with preference {:?} gets {:?}",
user_pref1, giveaway1
);
let user_pref2 = None;
let giveaway2 = store.giveaway(user_pref2);
println!(
"The user with preference {:?} gets {:?}",
user_pref2, giveaway2
);
}
The user with preference Some(Red) gets Red The user with preference None gets Blue
let expensive_closure = |num: u32| -> u32 {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};
fn add_one_v1 (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;
let v = Vec::new()
.let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5);
$ cargo run
Compiling closure-example v0.1.0 (file:///projects/closure-example)
error[E0308]: mismatched types
--> src/main.rs:5:29
|
5 | let n = example_closure(5);
| --------------- ^- help: try using a conversion method: `.to_string()`
| | |
| | expected struct `String`, found integer
| arguments to this function are incorrect
|
note: closure parameter defined here
--> src/main.rs:2:28
|
2 | let example_closure = |x| x;
| ^
For more information about this error, try `rustc --explain E0308`.
error: could not compile `closure-example` due to previous error
fn main() {
let list = vec![1, 2, 3];
println!("Before defining closure: {:?}", list);
let only_borrows = || println!("From closure: {:?}", list);
println!("Before calling closure: {:?}", list);
only_borrows();
println!("After calling closure: {:?}", list);
}
Before defining closure: [1, 2, 3] Before calling closure: [1, 2, 3] From closure: [1, 2, 3] After calling closure: [1, 2, 3]
fn main() {
let mut list = vec![1, 2, 3];
println!("Before defining closure: {:?}", list);
let mut borrows_mutably = || list.push(7);
// println!("Before calling closure: {:?}", list); // <- Ово не може!
borrows_mutably();
println!("After calling closure: {:?}", list);
}
Before defining closure: [1, 2, 3] After calling closure: [1, 2, 3, 7]
move
.use std::thread;
fn main() {
let list = vec![1, 2, 3];
println!("Before defining closure: {:?}", list);
thread::spawn(move || println!("From thread: {:?}", list))
.join()
.unwrap();
}
Before defining closure: [1, 2, 3] From thread: [1, 2, 3]
main
нит може завршити раније и тиме деалоцирати list
вредност
која би још увек била позајмљена од стране креиране нити.move
добићемо следећу грешку:error[E0373]: closure may outlive the current function, but it borrows `list`, which
is owned by the current function
--> src/main.rs:8:19
|
8 | thread::spawn(|| println!("From thread: {:?}", list))
| ^^ ---- `list` is borrowed here
| |
| may outlive borrowed value `list`
|
note: function requires argument type to outlive `'static`
--> src/main.rs:8:5
|
8 | thread::spawn(|| println!("From thread: {:?}", list))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `list` (and any other referenced variables),
use the `move` keyword
|
8 | thread::spawn(move || println!("From thread: {:?}", list))
| ++++
For more information about this error, try `rustc --explain E0373`.
error: could not compile `cargo0sMXjj` due to previous error
Fn
особинеFnOnce
- затворења која могу да се позову једном. Сва затворења
имплементирају бар ову особину, пошто могу да се позову бар једном.
Затворења која предају власништво запамћене вредности приликом позива
имплементрају само ову особину пошто се не могу позвати више од једном.FnMut
- затворења која не премештају вредности али их могу мењати. Могу се
позвати више од једном.Fn
- затворења која нити премештају нити мењају своје окружење, или не
памте ништа из окружења. Могу се позвати више пута.FnOnce
impl<T> Option<T> {
pub fn unwrap_or_else<F>(self, f: F) -> T
where
F: FnOnce() -> T
{
match self {
Some(x) => x,
None => f(),
}
}
}
unwrap_or_else
прихвата најопштији облик затворења која имплементирају FnOnce
.FnMut
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let mut list = [
Rectangle { width: 10, height: 1 },
Rectangle { width: 3, height: 5 },
Rectangle { width: 7, height: 12 },
];
list.sort_by_key(|r| r.width);
println!("{:#?}", list);
}
[ Rectangle { width: 3, height: 5, }, Rectangle { width: 7, height: 12, }, Rectangle { width: 10, height: 1, }, ]
sort_by_key
над исечком je FnMut
. Зашто?pub fn sort_by_key<K, F>(&mut self, f: F)
where
F: FnMut(&T) -> K,
K: Ord,
FnOnce
већ мора имати додатна ограничења.|r| r.width
не модификује својe окружење ипак је
ограничење тако дефинисано да буде најмање рестриктивно. Стога, пошто је
једини захтев да затворење можемо позивати више пута, ограничење које је
следеће најмање рестриктивно после FnOnce
је FnMut
.Fn
и FnMut
али не и она која су
само FnOnce
.FnOnce
.#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let mut list = [
Rectangle { width: 10, height: 1 },
Rectangle { width: 3, height: 5 },
Rectangle { width: 7, height: 12 },
];
let mut sort_operations = vec![];
let value = String::from("by key called");
list.sort_by_key(|r| {
sort_operations.push(value);
r.width
});
println!("{:#?}", list);
}
error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure
--> src/main.rs:19:30
|
16 | let value = String::from("by key called");
| ----- captured outer variable
17 |
18 | list.sort_by_key(|r| {
| --- captured by this `FnMut` closure
19 | sort_operations.push(value);
| ^^^^^ move occurs because `value` has type `String`, which
does not implement the `Copy` trait
For more information about this error, try `rustc --explain E0507`.
error: could not compile `cargoHMNw8k` due to previous error
FnMut
.#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let mut list = [
Rectangle { width: 10, height: 1 },
Rectangle { width: 3, height: 5 },
Rectangle { width: 7, height: 12 },
];
let mut num_sort_operations = 0;
list.sort_by_key(|r| {
num_sort_operations += 1;
r.width
});
println!("{:#?}, sorted in {num_sort_operations} operations", list);
}
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
v1_iter
је итератор. Сам по себи не ради ништа док се не конзумира.let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter {
println!("Got: {}", val);
}
Got: 1 Got: 2 Got: 3
Iterator
особина и метода next
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// methods with default implementations elided
}
type Item
односно
Self::Item
. Стога, имплементација ове особине над неким типом захтева
дефиницију и придруженог типа који представља тип елемената над којима се врши
итерација. #[test]
fn iterator_demonstration() {
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
assert_eq!(v1_iter.next(), None);
}
next
врши конзумацију итератора, односно узима се наредни
елемент и ажурира се интерно стање итератора.iter
метода производи итератор кроз непромењиве (дељене)
референце над елементима вектора, тако да next
враћа непромењиву референцу на
наредни елемент.iter_mut
.into_iter
.
У том случају итератор постаје власник колекције и враћа премештене вредности
при сваком позиву next
методе.Iterator
особина има богат API.next
методу. За такве методе кажемо да су
конзумирајући адаптери јер врше конзумацију итератора.sum
метода.let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
// println!("{:?}", v1_iter.next()); // <- Ово не може.
assert_eq!(total, 6);
sum
више нисмо у стању да позовемо v1_iter
јер смо га
конзумирали.let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1);
$ cargo run
Compiling iterators v0.1.0 (file:///projects/iterators)
warning: unused `Map` that must be used
--> src/main.rs:4:5
|
4 | v1.iter().map(|x| x + 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: iterators are lazy and do nothing unless consumed
= note: `#[warn(unused_must_use)]` on by default
warning: `iterators` (bin "iterators") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 0.47s
Running `target/debug/iterators`
collect
која ће да конзумира итератор и креира колекцију са елементима које
он враћа.let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);
collect
генеричка
метода која може да креира вредности различитог типа.#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
style: String,
}
fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}
let shoes = vec![
Shoe {
size: 10,
style: String::from("sneaker"),
},
Shoe {
size: 13,
style: String::from("sandal"),
},
Shoe {
size: 10,
style: String::from("boot"),
},
];
let in_my_size = shoes_in_size(shoes, 10);
assert_eq!(
in_my_size,
vec![
Shoe {
size: 10,
style: String::from("sneaker")
},
Shoe {
size: 10,
style: String::from("boot")
},
]
);
for
петљом:pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents
.lines()
.filter(|line| line.contains(query))
.collect()
}
for
петље.let buffer: &mut [i32];
let coefficients: [i64; 12];
let qlp_shift: i16;
for i in 12..buffer.len() {
let prediction = coefficients.iter()
.zip(&buffer[i - 12..i])
.map(|(&c, &s)| c * s as i64)
.sum::<i64>() >> qlp_shift;
let delta = buffer[i];
buffer[i] = prediction as i32 + delta;
}
token_recognizers
.iter()
.map(|tr| {
(
tr.recognize(&context.input[context.position..]),
tr.token_kind(),
)
})
// Skip unsuccesful recognition
.skip_while(|(recognized, _)| recognized.is_none())
// Create tokens
.map(|(recognized, token_kind)| Token {
kind: token_kind,
value: recognized.unwrap(),
location: recognized.unwrap().location_span(context.location),
})
// Take the first token or return None if no tokens are found.
.next()
let max_actions = self
.table
.states
.iter()
.map(|x| x.actions.iter().filter(|x| !x.is_empty()).count())
.max()
.unwrap();
let mut terminals = state
.actions
.iter()
.enumerate()
.filter(|(_, actions)| !actions.is_empty())
.map(|(idx, _)| TermIndex(idx))
.collect::<Vec<_>>();
fn context_str(&self, position: usize) -> String {
format!(
"{:?}",
self[position - min(15, position)..position]
.iter()
.map(|x| format!("{x}"))
.chain(once("-->".to_string()))
.chain(self[position..].iter().map(|x| format!("{x}")).take(15))
.collect::<Vec<_>>()
)
}
let actions = state
.actions
.iter()
.enumerate()
.filter(|(_, a)| !a.is_empty())
.flat_map(|(i, a)| repeat(i).zip(a.iter()))
.map(|(i, a)| {
(
self.grammar.terminals[TermIndex(i)].name.clone(),
match a {
Action::Shift(s) => format!("Shift to {s}"),
Action::Reduce(p, l) => {
format!(
"Reduce for len {l} by: {}",
self.grammar.productions[*p]
.to_string(self.grammar)
)
}
Action::Accept => "Accept".into(),
},
)
})
.collect::<Vec<_>>();
Дефиниција из књиге: Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides: Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley Professional, 1994. (позната под називом The Gang of Four book - GoF):
Object-oriented programs are made up of objects. An object packages both data and the procedures that operate on that data. The procedures are typically called methods or operations.
pub
кључне речи.pub struct AveragedCollection {
list: Vec<i32>,
average: f64,
}
AveragedCollection
типа без утицаја на
кориснике док год не мењамо сигнатуре јавних метода.impl AveragedCollection {
pub fn add(&mut self, value: i32) {
self.list.push(value);
self.update_average();
}
pub fn remove(&mut self) -> Option<i32> {
let result = self.list.pop();
match result {
Some(value) => {
self.update_average();
Some(value)
}
None => None,
}
}
pub fn average(&self) -> f64 {
self.average
}
fn update_average(&mut self) {
let total: i32 = self.list.iter().sum();
self.average = total as f64 / self.list.len() as f64;
}
}
Summary
особине.enum
за ову
намену, као што смо видели раније.Draw
.pub trait Draw {
fn draw(&self);
}
Желимо да подржимо овакву методу:
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
Са оним што до сада знамо могли бисмо пробати нешто овако:
pub struct Screen<T: Draw> {
pub components: Vec<T>,
}
impl<T> Screen<T>
where
T: Draw,
{
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
Али је проблем што тип T
ће бити исти за све елементе вектора components
тј.
обавиће се мономорфизација. Значи да нећемо моћи да у вектор сместимо различите
GUI компоненте, нпр. Button
и TextField
.
У овом случају користимо објекте са особином (Trait Objects) употребом кључне
речи dyn
. С обзиром да конкретан тип објекта са особином није познат у време
компајлирања његова величина није позната па се увек користи кроз неки облик
индирекције/референце (нпр. &
или Box
).
pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
}
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
Сада можемо имплементирати различите GUI компоненте које све имају особину Draw
.
pub struct Button {
pub width: u32,
pub height: u32,
pub label: String,
}
impl Draw for Button {
fn draw(&self) {
// code to actually draw a button
}
}
use gui::Draw;
struct SelectBox {
width: u32,
height: u32,
options: Vec<String>,
}
impl Draw for SelectBox {
fn draw(&self) {
// code to actually draw a select box
}
}
И можемо у вектор ставити вредности ових типова.
use gui::{Button, Screen};
fn main() {
let screen = Screen {
components: vec![
Box::new(SelectBox {
width: 75,
height: 10,
options: vec![
String::from("Yes"),
String::from("Maybe"),
String::from("No"),
],
}),
Box::new(Button {
width: 50,
height: 10,
label: String::from("OK"),
}),
],
};
screen.run();
}
T
разрешава у време компајлирања и компајлер зна где се метода налази па
може да генерише кôд који ће је директно позвати.draft
, review
и published
.draft
стању.review
.published
.published
могу вратити садржај.main
функцијаuse blog::Post;
fn main() {
let mut post = Post::new();
post.add_text("I ate a salad for lunch today");
assert_eq!("", post.content());
post.request_review();
assert_eq!("", post.content());
post.approve();
assert_eq!("I ate a salad for lunch today", post.content());
}
Post
типаpub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
pub fn new() -> Post {
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}
}
trait State {}
struct Draft {}
impl State for Draft {}
State
дефинише дељену функционалност свих стања. За сада ова особина не дефинише
методе. Post
започиње животни циклус у стању Draft
.
Post
структуриimpl Post {
// --snip--
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
pub fn content(&self) -> &str {
""
}
}
impl Post {
// --snip--
pub fn request_review(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.request_review())
}
}
}
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
}
struct Draft {}
impl State for Draft {
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview {})
}
}
struct PendingReview {}
impl State for PendingReview {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
}
take
над Option
типом је потребан јер желимо да узмемо власништво над
вредношћу која је у self.state
али морамо оставити поље у валидном стању. take
ће заменити вредност са None
. Ово је могуће јер имамо &mut self
референцу.approve
методеimpl Post {
// --snip--
pub fn approve(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.approve())
}
}
}
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
}
struct Draft {}
impl State for Draft {
// --snip--
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}
struct PendingReview {}
impl State for PendingReview {
// --snip--
fn approve(self: Box<Self>) -> Box<dyn State> {
Box::new(Published {})
}
}
struct Published {}
impl State for Published {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}
content
методе у Post
типуВршимо делегацију истој методи текућег стања.
impl Post {
// --snip--
pub fn content(&self) -> &str {
self.state.as_ref().unwrap().content(self)
}
// --snip--
}
Метода as_ref
над Option
је дефинисана на следећи начин:
/// Converts from `&Option<T>` to `Option<&T>`.
pub const fn as_ref(&self) -> Option<&T> {
match *self {
Some(ref x) => Some(x),
None => None,
}
}
Пошто је стање типа Option<Box<dyn State>>
када позовемо as_ref
добијемо
Option<&Box<dyn State>>
. Да нисмо то урадили добили би грешку јер не можемо да
преместимо стање (са unwrap
) из позајмљене &self
вредности.
self.state.as_ref().unwrap().content(self)
Тип над којим се позива content
je &Box<dyn State>
. Deref
коерција ће омогућити
да се "прође кроз" &
и Box
тако да ће се позвати над типом који имплементира
State
.
Сада је потребно још имплементирати ову методу над State
типовима.
trait State {
// --snip--
fn content<'a>(&self, post: &'a Post) -> &'a str {
""
}
}
// --snip--
struct Published {}
impl State for Published {
// --snip--
fn content<'a>(&self, post: &'a Post) -> &'a str {
&post.content
}
}
State
особини.Published
редефинише ову имплементацију.fn main() {
let mut post = Post::new();
post.add_text("I ate a salad for lunch today");
assert_eq!("", post.content());
}
Post
тип представља објављену објаву и тренутно није могуће креирати ову вредност.DraftPost
кроз Post::new
.pub struct Post {
content: String,
}
pub struct DraftPost {
content: String,
}
impl Post {
pub fn new() -> DraftPost {
DraftPost {
content: String::new(),
}
}
pub fn content(&self) -> &str {
&self.content
}
}
impl DraftPost {
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
}
Једино DraftPost
има методу request_review
тако да је немогуће позвати ову
методу над објавама које су у другом стању.
impl DraftPost {
// --snip--
pub fn request_review(self) -> PendingReviewPost {
PendingReviewPost {
content: self.content,
}
}
}
pub struct PendingReviewPost {
content: String,
}
impl PendingReviewPost {
pub fn approve(self) -> Post {
Post {
content: self.content,
}
}
}
Једино PendingReviewPost
има approve
методу тако да није могуће одобрити објаве
у другим стањима.
Објаве се трансформишу овим методама (узима се власништво над претходном) да се
осигура да не постоје исте објаве у различитим стањима. Због овога је потребно
користити shadowing употребом let post = post.<metoda>
.
use blog::Post;
fn main() {
let mut post = Post::new();
post.add_text("I ate a salad for lunch today");
let post = post.request_review();
let post = post.approve();
assert_eq!("I ate a salad for lunch today", post.content());
}
Нити се извршавају конкурентно тако да не постоје инхерентне гаранције на редослед извршавања. Ово може довести до разних проблема:
use std::thread;
use std::time::Duration;
fn main() {
thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
hi number 1 from the main thread! hi number 1 from the spawned thread! hi number 2 from the main thread! hi number 2 from the spawned thread! hi number 3 from the main thread! hi number 3 from the spawned thread! hi number 4 from the main thread! hi number 4 from the spawned thread! hi number 5 from the spawned thread!
main
функцијом, заврши све остале нити се гасе.join
Да би сачекали да све нити заврше користимо повратну вредност thread::spawn
позива која је типа JoinHandle
. У питању је поседована вредност чија метода join
ће блокирати текућу нит док дата нит не заврши.
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap();
}
hi number 1 from the main thread! hi number 1 from the spawned thread! hi number 2 from the main thread! hi number 2 from the spawned thread! hi number 3 from the spawned thread! hi number 3 from the main thread! hi number 4 from the spawned thread! hi number 4 from the main thread! hi number 5 from the spawned thread! hi number 6 from the spawned thread! hi number 7 from the spawned thread! hi number 8 from the spawned thread! hi number 9 from the spawned thread!
Ако преместимо handle.join()
пре петље.
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
handle.join().unwrap();
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
hi number 1 from the spawned thread! hi number 2 from the spawned thread! hi number 3 from the spawned thread! hi number 4 from the spawned thread! hi number 5 from the spawned thread! hi number 6 from the spawned thread! hi number 7 from the spawned thread! hi number 8 from the spawned thread! hi number 9 from the spawned thread! hi number 1 from the main thread! hi number 2 from the main thread! hi number 3 from the main thread! hi number 4 from the main thread!
Често нам треба да нит позајми вредност из текуће нити. У следећем примеру
вектор v
се покушава читати из нити.
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(|| {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
}
Али се овај кôд не компајлира.
error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function --> src/main.rs:7:32 | 7 | let handle = thread::spawn(|| { | ^^ may outlive borrowed value `v` 8 | println!("Here's a vector: {:?}", v); | - `v` is borrowed here | note: function requires argument type to outlive `'static` --> src/main.rs:7:18 | 7 | let handle = thread::spawn(|| { | __________________^ 8 | | println!("Here's a vector: {:?}", v); 9 | | }); | |______^ help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword | 7 | let handle = thread::spawn(move || { | ++++ For more information about this error, try `rustc --explain E0373`.
Раст инференца проналази да се вектор треба позајмити путем дељене референце али нема гаранције да ће позајмљена вредност бити "жива" када је нит буде користила.
У овом примеру користимо drop
па је још јасније зашто је ово проблем.
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(|| {
println!("Here's a vector: {:?}", v);
});
drop(v); // oh no!
handle.join().unwrap();
}
Решење је да се вектор премести у нит употребом кључне речи move
.
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
}
Ово решава проблем са оба претходна примера. Али је једино први пример валидан.
Није могуће користити drop
над премештеном вредношћу.
$ cargo run Compiling threads v0.1.0 (file:///projects/threads) error[E0382]: use of moved value: `v` --> src/main.rs:10:10 | 4 | let v = vec![1, 2, 3]; | - move occurs because `v` has type `Vec<i32>`, which does not implement the `Copy` trait 5 | 6 | let handle = thread::spawn(move || { | ------- value moved into closure here 7 | println!("Here's a vector: {:?}", v); | - variable moved due to use in closure ... 10 | drop(v); // oh no! | ^ value used here after move For more information about this error, try `rustc --explain E0382`. error: could not compile `threads` due to previous error
Правила власништва одбацују невалидан кôд и лепо се уклапају и у причу о вишенитном програмирању.
Слање порука је све популарнији модел конкурентног програмирања где се сигурност постиже разменом порука између нити.
Један од слогана Го програмског језика је:
Do not communicate by sharing memory; instead, share memory by communicating.
mpsc::channel()
mpsc
је модул у стандардној библиотеци који омогућава овај стил конкурентног програмирања.use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
recv
је блокирајући позив који чека да прихвати поруку. Враћа Result<T, E>
тип. Уколико се добије грешка то је знак да је пошиљалац затворио свој крај
конекције и да поруке више неће стизати.try_recv
који није блокирајућ.
Покушај употребе вредности val
после слања.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
println!("val is {}", val);
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
error[E0382]: borrow of moved value: `val` --> src/main.rs:11:35 | 9 | let val = String::from("hi"); | --- move occurs because `val` has type `String`, which does not implement the `Copy` trait 10 | tx.send(val).unwrap(); | --- value moved here 11 | println!("val is {}", val); | ^^^ value borrowed here after move | help: consider cloning the value if the performance cost is acceptable | 10 | tx.send(val.clone()).unwrap(); | ++++++++ For more information about this error, try `rustc --explain E0382`.
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {}", received);
}
}
Got: hi Got: from Got: the Got: thread
// --snip--
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
// --snip--
// --snip--
thread::spawn(move || {
let vals = vec![
String::from("more"),
String::from("messages"),
String::from("for"),
String::from("you"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {}", received);
}
// --snip--
mutex
структура.Mutex
структуре имају репутацију да су тешки за употребу зато што се морају
строго поштовати два правила:
mutex
да би друге нити
могле да га користе.mutex
-а неправилно спроведе.Mutex<T>
APIuse std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("m = {:?}", m);
}
Mutex<T>
је wrapper тип који садржи у себи податак који се дели.m
је Mutex<i32>
а не i32
.lock()
која ће да блокира док се
не добије ексклузиван приступ. Дакле, немогуће је заборавити да се mutex
закључа јер је то једини начин да се приступи податку.lock()
враћа LockResult
јер може да не успе уколико нит која држи закључан
ресурс прекине са радом кроз panic
.LockResult
уколико успе у себи садржи MutexGuard
паметни поинтер који
имплементира Deref
, да би могао да врати интерне податке, и Drop
да би
аутоматски откључао ресурс када изађе из опсега важења.Mutex<T>
између нитиuse std::sync::Mutex;
use std::thread;
fn main() {
let counter = Mutex::new(0);
let mut handles = vec![];
for _ in 0..10 {
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
error[E0382]: borrow of moved value: `counter` --> src/main.rs:22:29 | 6 | let counter = Mutex::new(0); | ------- move occurs because `counter` has type `Mutex<i32>`, which does not implement the `Copy` trait ... 10 | let handle = thread::spawn(move || { | ------- value moved into closure here, in previous iteration of loop ... 22 | println!("Result: {}", *counter.lock().unwrap()); | ^^^^^^^ value borrowed here after move
Rc<T>
за вишеструко власништвоuse std::rc::Rc;
use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Rc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Rc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely --> src/main.rs:12:36 | 12 | let handle = thread::spawn(move || { | ------------- ^------ | | | | ______________________|_____________within this `{closure@src/main.rs:12:36: 12:43}` | | | | | required by a bound introduced by this call 13 | | let mut num = counter.lock().unwrap(); 14 | | 15 | | *num += 1; 16 | | }); | |_________^ `Rc<Mutex<i32>>` cannot be sent between threads safely | = help: within `{closure@src/main.rs:12:36: 12:43}`, the trait `Send` is not implemented for `Rc<Mutex<i32>>` note: required because it's used within this closure --> src/main.rs:12:36 | 12 | let handle = thread::spawn(move || { | ^^^^^^^ note: required by a bound in `spawn` --> /home/igor/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/mod.rs:683:8 | 680 | pub fn spawn<F, T>(f: F) -> JoinHandle<T> | ----- required by a bound in this function ... 683 | F: Send + 'static, | ^^^^ required by this bound in `spawn`
`Rc<Mutex<i32>>` cannot be sent between threads safely
trait `Send` is not implemented for `Rc<Mutex<i32>>`
Send
је имплементирана само за типове који могу да се шаљу
између нити.Rc<T>
тип не имплементира Send
јер није сигуран у контексту нити (thread-safe).Arc<T>
Rc<T>
типа која је сигурна у контексту нити и стога имплементира Send
.Arc<T>
у
контексту једне нити. use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
Result: 10
RefCell<T>/Rc<T>
и Mutex<T>/Arc<T>
counter
је непромењив али можемо добити промењиву позајмицу тј. Mutex<T>
омогућава унутрашњу промењивост као и RefCell<T>
.Send
и Sync
особинеSend
имплементирају типови чије вредности се безбедно могу прености између нити.Send
али има изузетака (нпр. Rc<T>
). Такође, сви
сложени типови који се састоје искључиво од Send
типова су Send
.Sync
имплементирају типови чије вредности се могу позајмљивати између нити.T
je Sync
ako je &T
Send
.Sync
. Сложени типови који се састоје искључиво од
Sync
типова су Sync
.