Креирано 2023-01-16 Mon 18:17, притисни 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)
За ажурирање инсталације на најновију верзију:
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
имплементиран на Ok
варијанти ће вратити вредност која је садржана унутар
варијанте док имплементација над Err
варијанти прекида извршавање програма.
$ 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);
}
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);
}
Приступ елементима торке:
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.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() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("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);
}
Пример: 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);
}
Вредност функције је вредност блока који представља тело функције, дакле последњег израза унутар тела функције.
fn five() -> i32 {
5
}
fn main() {
let x = five();
println!("The value of x is: {}", x);
}
Или на пример:
fn main() {
let x = plus_one(5);
println!("The value of x is: {}", x);
}
fn plus_one(x: i32) -> i32 {
x + 1
}
Уколико израз 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");
}
}
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");
}
}
if
у let
исказима
if
је израз па се може користити где год можемо писати и било који други израз.
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {}", number);
}
Али се гране морају слагати по типу. Ово је погрешно јер је прва грана типа
i32
док је else
грана типа str
.
fn main() {
let condition = true;
let number = if condition { 5 } else { "six" };
println!("The value of number is: {}", number);
}
Р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);
}
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);
}
while
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("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;
}
}
Али је за ту намену природније и сигурније користити for
петљу. Такође ће се
програм брже извршавати.
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {}", element);
}
}
for
петље су најчешћи облик петљи у употреби у Расту. Користе се нпр. и у
ситуацији када је потребно извршити петљу одређени број пута.
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
{ // 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]);
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();
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
као машина за сортирање новчића
За почетак можемо 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
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() {}
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;
}
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);
}
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 мора да садржи животни веку ресулт варијабле
Што не може бити испуњено истовремено, тј. не постоји '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
што не може бити валидно јер варијабла живи дуже
од вредности string2
.
Иако, можемо видети да ће, због дужине стринга, враћена вредност бити string1
и
програм је валидан, компајлер ово не може да закључи.