Node-js 開發者的 Rust 入門指南

作者 | Florian GOTO       

譯者 | 彎月    責編 | 歐陽姝黎

出品 | CSDN(ID:CSDNnews)

以下爲譯文:

隨着 WebAssembly 的進步,如果你想在 JavaScript 和 Node.js 的基礎上,提高瀏覽器、服務器和邊緣計算的性能,那麼可以瞭解一下 Rust。

Node.js 技術棧與 Rust 的結合簡直是天作之合,因爲 Rust 能提供 WebAssembly 支持,而 WebAssembly 能在 Node.js 上運行。

本文將詳細地介紹如何在 Node.js 上編譯 Rust,並運行 WebAssembly。

注意:

對於以 JavaScript 爲主的 Node.js 開發者來說,你可能不太熟悉類似於 “std::wx::y” 或“&xyz”之類的表述,但是沒關係,我會詳細解釋。

與 JavaScript 和 Node.js 相比,Rust 是一門較爲低級的語言。這意味着,你需要熟悉計算機的工作原理,才能真正理解 Rust。而 Node.js 更爲高級,通常接觸不到這些表述。

別忘了,Rust 最初是一門非常接近底層硬件的系統編程語言。這樣能獲得更高的性能,但也會導致更高的複雜性。

Rust 不會隱藏變量位於棧上還是堆上、以及因此導致的限制等細節。但它也提供了大量的庫和模塊(在 Rust 中稱爲 crate),這一點很像 Node.js,因此編程難度並不高。

創建一個 Rust 項目

本文所有的代碼都可以利用 Rust playground 在線運行(當然,除了那些需要訪問本地的代碼之外)。

在安裝好 Rust 之後,利用 cargo 命令(Rust 的包管理器)創建一個新項目:

cargo new <PROJECT_NAME>

這個命令將在當前目錄下創建一個新文件夾。

或者你也可以將當前目錄作爲項目文件夾:

cargo init
源代碼位於src/目錄下,入口爲main.rs文件中的main函數(用fn關鍵字定義)。
fn main() {
println!("Hello, world!");
}

輸出

Rust 使用 “宏” 來輸出到控制檯。Rust 中的宏用感嘆號(!)表示。println! 宏非常靈活:

fn main() {
// string interpolation
println!("Adding {} and {} gives {}", 22, 33, 22 + 33);
// positional arguments
println!(
"Ypur name is {0}. Welcome to {1}. Nice to meet you {0}",
"Goto", "Rust"
);
// named arguments
println!(
"{language} is very popular. It was created in {year}",
language = "Rust",
year = 2010
);
// placeholder traits (using positional argument to avoid repeat)
println!("{0}, in binary: {0:b}, in hexadecimal: {0:x}", 11);
// debug trait (very useful to print anything)
// if you try to print the array directly, you will get an error
// because an array is not a string or number type
println!("{:?}", [11, 22, 33]);
}

運行代碼查看輸出:

cargo run

你將會看到下面的結果(以及一大堆編譯信息——Rust 是一門編譯語言):

Adding 22 and 33 gives 55
Ypur name is Goto. Welcome to Rust. Nice to meet you Goto
Rust is very popular. It was created in 2010
Decimal: 11      Binary: 1011    Hexadecimal: b
[11, 22, 33]

在 Rust 中,行尾必須使用分號(;),除非是函數最後一行的返回語句(稍後進一步解釋)。

對數值輸出進行高級格式化

fn main() {
let x = 246.92385;
let y = 24.69;
let z = x / y;
// print line macro with 3 decimal point precision
println!("z is {:.3}", z);
// 9: total character space the number to occupy
// (adds pre padding if necessary)
println!("z is {:9.3}", z);
// 0: placeholder number for padding characters
println!("z is {:09.3}", z);
println!("z is {:09.3}\nx is {}", z, x);
// print macro without new line
print!("y is {:09.3}\n x is {}\n", y, x);
// positional parameters
println!("z is {0:05.1} and x is {1:.2}. \nx is also {1}", z, x)
}

輸出結果:

z is 10.001
z is    10.001
z is 00010.001
z is 00010.001
x is 246.92385
y is 00024.690
x is 246.92385
z is 010.0 and x is 246.92.
x is also 246.92385

變量

fn main() {
// variables are immutable by default
let pc = "Inspirion XYZ";
println!("pc is {}", pc);
// mutable variables
let mut age = 1;
println!("age is {}", age);
age = 2;
println!("age is {}", age);
// constants (must be uppercase and explicit type definition)
const BRAND: &str = "Dell";
println!("brand is {}", BRAND);
// multiple assignment (tuple destructuring)
// more on tuples later in the article
let (status, code) = ("OK", 200);
println!("status: {}, code: {}", status, code);
}

輸出結果:

pc is Inspirion XYZ
age is 1
age is 2
brand is Dell
status: OK, code: 200

基本類型

fn main() {
// default integer numeric type is i32
let num1 = 123;
println!("{} - type: {}", num1, get_type(&num1));
// default floating point numeric type is f64
let num2 = 1.23;
println!("{} - type: {}", num2, get_type(&num2));
// explicit typing
let num3: i8 = 23;
println!("{} - type: {}", num3, get_type(&num3));
// max values
// std is the standard library/crate,
// it gives access to a rich variety of features,
// here we use the type modules (i32, i16, etc.) and properties
let max_i32 = std::i32::MAX;
let max_i16 = std::i16::MAX;
println!("max value for i32 is {}", max_i32);
println!("max value for i16 is {}", max_i16);
// boolean
let is_rust_fun: bool = true;
println!(
"is_rust_fun is {} - type: {}",
is_rust_fun,
get_type(&is_rust_fun)
);
let is_greater = 23 > 5;
println!(
"is_greater is {} - type: {}",
is_greater,
get_type(&is_greater)
);
// characters (unicode - up to 4 bytes length)
let smiley = '😈';
println!("smiley is {} - type: {}", smiley, get_type(&smiley));
}
// helper function to print types
fn get_type<T>(_: &T) -> &str {
std::any::type_name::<T>()
}

輸出結果:

123 - type: i32
1.23 - type: f64
23 - type: i8
max value for i32 is 2147483647
max value for i16 is 32767
is_rust_fun is true - type: bool
is_greater is true - type: bool
smiley is 😈 - type: char

浮點數

fn main() {
// by default fractional values stored in f64
let my_float = 12.345677890123456789012345;
println!("my_float is: {}", my_float);
let a_float: f32 = 9.9438535983578493758;
println!("a_float is: {}", a_float);
let min_f32 = std::f32::MIN;
println!("min_f32 is: {}\n", min_f32);
let max_f32 = std::f32::MAX;
println!("max_f32 is: {}\n", max_f32);
let min_f64 = std::f64::MIN;
println!("min_f64 is: {}\n", min_f64);
let max_f64 = std::f64::MAX;
println!("max_f64 is: {}\n", max_f64);
}

輸出結果:

my_float is: 12.345677890123456
a_float is: 9.943853
min_f32 is: -340282350000000000000000000000000000000
max_f32 is: 340282350000000000000000000000000000000
min_f64 is: -179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
max_f64 is: 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

位操作(高級內容,可以跳過)

=====================

/*
Bitwise operations: on individual bits rather than sets of bytes.
- binary representation, a sequence of bytes
- underscore separator allowed for legibility
- by default binary representations are store as i32
*/
fn main() {
// stored as u8 by adding suffix u8
let mut value = 0b1111_0101u8;
// will print base 10 (decimal) representation
println!("value is {}", value);
/*
:08b
0 -> display leading zeros
8 -> number of bits to display
b -> display binary representation
*/
println!("value is {:08b}", value);
// bitwise NOT: invert individual bits
value = !value; // 0000_1010
println!("value is {:08b}", value);
// bitwise AND: used to clear the value of a specific bit
value = value & 0b1111_0111; // -> 0000_0010
println!("value is {:08b}", value);
// bitwise AND: used to check value of a specific bit
// if a specific bit is 0 or 1, useful to check status of registers for process state
println!("value is {:08b}", value & 0b0100_0000);
// -> 0000_0000
// bitwise OR: if either operand is 1, result is 1
// useful to set value of a specific bit
value = value | 0b0100_0000; // -> 0100_0010
println!("value is {:08b}", value);
// bitwise XOR (exclusive OR):
// result is 1 only when bits are different, otherwise 0
// useful to set if bits are different
value = value ^ 0b0101_0101; // -> 0001_0111
println!("value is {:08b}", value);
////////////////////////////
// Bit Shift operators
////////////////////////////
// shift bit pattern left or right by a number of bits
// and backfill shifted bit spaces with zeros
// shift left by 4 bits
value = value << 4; // -> 0111_0000
println!("value is {:08b}", value);
// shift right by 3 bits
value = value >> 3; // -> 0000_1110
println!("value is {:08b}", value);

輸出結果:

value is 245
value is 11110101
value is 00001010
value is 00000010
value is 00000000
value is 01000010
value is 00010111
value is 01110000
value is 00001110

布爾和二進制代數

fn main() {
let a = true;
let b = false;
println!("a is {}\nb is {}", a, b);
println!("NOT a is {}", !a);
println!("a AND b is {}", a & b);
println!("a OR b is {}", a | b);
println!("a XOR b is {}", a ^ b);
// boolean casted to integer begets 0 or 1
println!("a XOR b is {}", (a ^ b) as i32); // 1
let c = (a ^ b) | (a & b);
println!("c is {}", c);
// short-circuiting logical operations:
// right operand not evaluated
let d = true || (a & b);
println!("d is {}", d);
// the panic macro is not evaluated,
// so the process ends with status 0 (OK, no error)
// panics exit the program immediately (like throwing error in Node.js)
let e = false && panic!();
println!("e is {}", e);
}

輸出結果:

a is true
b is false
NOT a is false
a AND b is false
a OR b is true
a XOR b is true
a XOR b is 1
c is true
d is true
e is false

算術操作

fn main() {
// can only do arithmetic operations on same type operands
let a = 11;
let b = 33;
let c = a + b;
println!("c is {}", c);
let d = c - b;
println!("d is {}", d);
let e = a * d;
println!("e is {}", e);
// type casting (careful with precision loss and type compatibility)
let f = c as f32 / 4.5;
println!("f is {}", f);
// operator precedence control
let g = 43.5432 % (a as f64 * e as f64);
println!("g is {}", g);
}

輸出結果:

c is 44
d is 11
e is 121
f is 9.777778
g is 43.5432

比較操作

/*
can only compare values of same type
*/
fn main() {
let a = 11;
let b = 88;
println!("a is {}\nb is {}", a, b);
println!("a EQUAL TO b is {}", a == b);
println!("a NOT EQUAL TO b is {}", a != b);
println!("a GREATER THAN b is {}", a > b);
println!("a GREATER THAN OR EQUAL TO b is {}", a >= b);
println!("a LESS THAN b is {}", a < b);
println!("a LESS THAN OR EQUAL TO b is {}", a <= b);
let c = true;
let d = false;
println!("\nc is {}\nd is {}", c, d);
println!("c EQUAL TO d is {}", c == d);
println!("c NOT EQUAL TO d is {}", c != d);
println!("c GREATER THAN d is {}", c > d);
println!("c GREATER THAN OR EQUAL TO d is {}", c >= d);
println!("c LESS THAN d is {}", c < d);
println!("c LESS THAN OR EQUAL TO d is {}", c <= d);
}

輸出結果:

a is 11
b is 88
a EQUAL TO b is false
a NOT EQUAL TO b is true
a GREATER THAN b is false
a GREATER THAN OR EQUAL TO b is false
a LESS THAN b is true
a LESS THAN OR EQUAL TO b is true
c is true
d is false
c EQUAL TO d is false
c NOT EQUAL TO d is true
c GREATER THAN d is true
c GREATER THAN OR EQUAL TO d is true
c LESS THAN d is false
c LESS THAN OR EQUAL TO d is false

字符

fn main() {
// Unicode scalar value stored using 4 bytes (32 bits)
// contrary to C like languages that store it in 1 byte
let letter: char = 'z';
let number_char = '9';
let finger = '\u{261D}';
println!("letter is {}", letter);
println!("number_char is {}", number_char);
println!("finger is {}", finger);
}

輸出結果:

letter is z
number_char is 9
finger is ☝

計算平均值

fn main() {
let a = 33;
let b = 4.9;
let c: f32 = 123.5;
let average = (a as f32 + b as f32 + c) / 3.0;
println!("average is {}", average);
assert_eq!(average, 53.8);
println!("test passed.");
}

輸出結果:

average is 53.8
test passed.

數組

fn main() {
// fixed length and single typed
// stored in contiguous memory locations
let letters = ['a', 'b', 'c']; // type: [char; 3]
let first_letter = letters[0];
println!("first_letter is {}", first_letter);
// to modify elements in array, it must be mutable
let mut numbers = [11, 22, 44]; // type is [i32; 3]
numbers[2] = 33;
println!("numbers is {}", numbers[2]);
// empty array declaration (memory allocated)
let words: [&str; 2];
words = ["ok"; 2]; // repeat expression, equivalent to ["ok", "ok"]
println!("words is {:?}", words);
/*
length of usize is based on number of bytes needed to reference memory in your target architecture:
- for 32 bit compilation target -> usize is 4 bytes
- for 64 bit compilation target -> usize is 8 bytes
*/
let ints = [22; 5];
let length: usize = ints.len();
println!("length is {}", length);
// get size in memory (mem module of the std crate)
let mem_size_byte = std::mem::size_of_val(&ints);
println!("mem_size_byte is {}", mem_size_byte);
// get slice from array
let mut slice: &[i32] = &ints;
println!("slice is {:?}", slice);
slice = &ints[3..5];
println!("slice is {:?}", slice);
}

輸出結果:

first_letter is a
numbers is 33
words is ["ok", "ok"]
length is 5
mem_size_byte is 20
slice is [22, 22, 22, 22, 22]
slice is [22, 22]

多維數組

fn main() {
let d2: [[i32; 3]; 3] = [[9, 8, 7], [6, 5, 4], [3, 2, 1]];
let value = d2[1][0];
println!("value is {}", value);
// mutating a tuple
let d3: [[[&str; 100]; 20]; 5];
d3 = [[["ok"; 100]; 20]; 5];
println!("value d3[3][11][35] is {}", d3[3][11][35])
}

輸出結果:

value is 6
value d3[3][11][35] is ok

向量

fn main() {
// vectors = mutable size arrays
let mut letters: Vec<char> = vec!['a', 'b', 'c'];
println!("letters are {:?}", letters);
let first_letter = letters[0];
println!("first_letter is {}", first_letter);
// add value to vector
letters.push('d');
letters.push('e');
letters.push('f');
println!("letters are {:?}", letters);
// remove last value
letters.pop();
println!("letters are {:?}", letters);
let mut numbers: Vec<i32> = vec![11, 22, 44];
numbers[2] = 33;
println!("numbers is {}", numbers[2]);
let words: Vec<&str>;
words = vec!["ok"; 2];
println!("words are {:?}", words);
let mut ints = vec![22, 33, 44, 55, 66, 77];
let length: usize = ints.len();
println!("length is {}", length);
let mem_size_byte = std::mem::size_of_val(&ints);
println!("mem_size_byte is {}", mem_size_byte);
// slice from vector
let mut slice: &[i32] = &ints;
println!("slice is {:?}", slice);
slice = &ints[2..5];
println!("slice is {:?}", slice);
// iterate over vector
for it in ints.iter() {
println!("it is {}", it);
}
// mutate vector items while iterating
for it in ints.iter_mut() {
// dereference the pointer to get and set value (*it)
*it *= *it;
}
println!("ints is {:?}", ints);
}

輸出結果:

letters are ['a', 'b', 'c']
first_letter is a
letters are ['a', 'b', 'c', 'd', 'e', 'f']
letters are ['a', 'b', 'c', 'd', 'e']
numbers is 33
words is ["ok", "ok"]
length is 6
mem_size_byte is 24
slice is [22, 33, 44, 55, 66, 77]
slice is [44, 55, 66]
it is 22
it is 33
it is 44
it is 55
it is 66
it is 77
ints is [484, 1089, 1936, 3025, 4356, 5929]

元組

fn main() {
// can have max 12 mixed type values
// adding more values and it will no longer be a tuple type
let a_tuple: (&str, u8, char) = ("ok", 0, 'd');
let first_item = a_tuple.0;
println!("first_item is {}", first_item);
// mutate a tuple
let mut b_tuple = ("ok", 0);
b_tuple.0 = "ko";
b_tuple.1 += 1;
println!("b_tuple.1 is {}", b_tuple.1);
// destructure a tuple
let c_tuple = ("en", "US", 1);
let (language, country, code) = c_tuple;
println!(
"language is: {}\ncountry is: {}\ncode is: {}",
language, country, code
)
}

輸出結果:

first_item is ok
b_tuple.1 is 1
language is: en
country is: US
code is: 1

函數

fn main() {
be_polite();
// inferred types for y and z are the ones used as parameters of add()
// to be clear, if you do not declare a specific type for variables, these variables will assume the type of the arguments of the function where first used
// remember, by the default inferred type is i32 for integers
let y = 12;
let z = 34;
// now y and z are considered u8 type because this is how they are first used as function arguments
add(y, z);
// passing later y and z to another fn with different param types will panic
// guess_number(z) // -> expects a i32 not a u8
// need for explicit cast:
guess_number(y as i32)
}
fn be_polite() {
println!("Greetings, pleased to meet you.");
guess_number(25)
}
fn guess_number(number: i32) {
println!("Indeed, {} is the correct answer", number)
}
fn add(a: u8, b: u8) {
let sum = a + b;
println!("sum is {}", sum)
}

輸出結果:

Greetings, pleased to meet you.
Indeed, 25 is the correct answer
sum is 46
Indeed, 12 is the correct answer

語句和表達式

=============

fn main() {
// Statement performs an action without returning a value
// statements end with a semicolon: a = 6;
// an expression evaluates to a resulting value
// expressions do NOT end with a semicolon: 3 + 4 which evaluates to 7
// adding a semicolon to an expressions transforms it into an statement
// expressions are used as parts of statements: let total = r + c;\n\t{}\n\t{}",
// where "r + c" is an expression and "let total = r + c;" is a statement
println!("expression 4 + 5 evaluates to: {}", 4 + 5);
}

輸出結果:

expression 4 + 5 evaluates to: 9

函數返回類型

fn main() {
let result = square(3);
println!("result is {}", result);
let result_tuple = triple(33);
let (input, result1) = result_tuple;
println!("result_tuple is {:?}", result_tuple);
// {:?} ==> debug formatting
println!("input {} evaluates to {}", input, result1);
let nothing: () = does_not_return();
println!("nothing (union data type) is {:?}", nothing)
}
fn square(number: i32) -> i32 {
println!("processing square({})", number);
// expression returning a value
number * number
// " return  number * number;" is also valid syntax
}
// multiple returns with tuples
fn triple(number: i32) -> (i32, i32) {
println!("tripling the number: {}", number);
let input = number;
let result = number * 3;
(input, result)
}
// union data type
// used when no meaningful values returned by a fn
// represented by empty ()
// it is optional
fn does_not_return() -> () {
println!("ain't returning nuthing!")
}

輸出結果:

processing square(3)
result is 9
tripling the number: 33
result_tuple is (33, 99)
input 33 evaluates to 99
ain't returning nuthing!
nothing (union data type) is ()

閉包

fn main() {
// closures are anonymous functions that have access to variables in the enclosing scope
// long form
let double = |n1: u8| -> u8 { n1 * 2 };
// short form
let triple = |n1| n1 * 3;
const DAYS_IN_YEAR: u16 = 365;
// referencing variable from enclosing scope
let quadruple_than_add_number_days_in_year = |n1: i32| n1 * 4 + (DAYS_IN_YEAR as i32);
const FACTOR: i32 = 22;
let multiple_by_22 = |x| FACTOR * x;
println!("{}", double(11));
println!("{}", triple(99));
println!("{}", quadruple_than_add_number_days_in_year(44));
println!("{}", multiple_by_22(5));
}

輸出結果:

22
297
541
110

攝氏度到華氏度轉換

fn main() {
let (celsius, farenheit) = to_farenheit(40.0);
println!("{} celsius is {} farenheit", celsius, farenheit);
assert_eq!(farenheit, 104.0);
// will not execute if assertion fails
println!("test passed");
}
fn to_farenheit(celsius: f32) -> (f32, f32) {
let farenheit = (1.8 * celsius) + 32.0;
// return statement (no semicolon)
(celsius, farenheit)
}

條件執行

fn main() {
let x = 5;
if x == 5 {
println!("x is 5");
}
// if expressions (equivalent of ternary operator in JS/Node.js)
let x_odd = if x % 2 == 0 { "odd" } else { "even" };
println!("x_odd is {}", x_odd);
}

輸出結果:

x is 5
x_odd is even

多重條件(if/else if)

fn main() {
let x = 2;
let y = 5;
if x > y {
println!("x is greater than  y");
} else if x < y {
println!("x is less than y");
} else {
println!("x is equal to y");
}
}

輸出結果:

x is less than y

循環賦值

fn main() {
let mut count = 0;
// infinite loop
loop {
if count == 10 {
break;
}
count += 1;
println!("count is {}", count);
}
println!("\nAfter first loop.\n");
// returning a value from loop expression
let result = loop {
if count == 15 {
// returning a value with break statement
break count * 20;
}
count += 1;
println!("count is {}", count);
};
println!("\nAfter second loop, result is {}", result);
}

輸出結果:

count is 1
count is 2
count is 3
count is 4
count is 5
count is 6
count is 7
count is 8
count is 9
count is 10
After first loop.
count is 11
count is 12
count is 13
count is 14
count is 15
After second loop, result is 300

while 循環

fn main() {
let mut count = 0;
let letters: [char; 5] = ['a', 'b', 'c', 'd', 'e'];
while count < letters.len() {
println!("letter[{}] is {}", count, letters[count]);
count += 1;
}
// contrary to loop expressions, the break statement in while loop cannot return a value
}

輸出結果:

letter[0] is a
letter[1] is b
letter[2] is c
letter[3] is d
letter[4] is e

for 循環

fn main() {
let message = ['m', 'e', 's', 's', 'a', 'g', 'e'];
/* Iterator
- implements logic to iterate over each item in a collection
- next() method returns the next item in a sequence
*/
for item in message.iter() {
println!("current item is {}", item);
}
println!("");
// to also get the indexes when iterating
// enumerate() returns a tuple with index/item_reference pair
// to get the item use &item
// because the iterator gives back a reference (&<NAME>)
// if you don't use the &, you get the reference not the value
// adding the & allows you to borrow the variable without taking ownership (more on that later) - then when you use the variable in the for loop scope, you access the value
for (index, &item) in message.iter().enumerate() {
println!("item {} is {}", index, item);
if item == 'e' {
break;
}
}
println!("");
// iterating over a range of numbers
// excludes the end value of the range
for number in 0..5 {
println!("number is {}", number);
}
}

輸出結果:

current item is m
current item is e
current item is s
current item is s
current item is a
current item is g
current item is e
item 0 is m
item 1 is e
number is 0
number is 1
number is 2
number is 3
number is 4

嵌套循環

fn main() {
let mut matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
// reading from matrix
for row in matrix.iter() {
for number in row.iter() {
print!("{}\t", number);
}
println!("");
}
println!("=======================");
// modifying values from mutable matrix
// iter_mut() returns mutable references
for row in matrix.iter_mut() {
for number in row.iter_mut() {
// dereference with asterisk to get the value itself
*number += 20;
print!("{}\t", number);
}
println!("");
}
}

輸出結果:

1 2 3
4 5 6
7 8 9
=======================
21 22 23
24 25 26
27 28 29

猜數遊戲

use rand::Rng;
use std::io;
fn main() {
println!("Guess a number");
println!("Please enter your guess:");
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("The secret number is {}", secret_number);
// "::" is used for associated functions of a given type (equiv to static methods in OOP)
// String::new() creates an empty string of type String    (growable UTF-8 encoded text)
let mut guess = String::new();
/*
std::io::stdin, if you don't use the import at the top of file
std::io::stdin() returns an instance of a std::io::Stdin type
*/
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guess: {}", guess);
}

統計基礎

fn main() {
let numbers = [1, 9, -2, 0, 23, 20, -7, 13, 37, 20, 56, -18, 20, 3];
let mut max: i32 = numbers[0];
let mut min: i32 = numbers[0];
let mut mean: f64 = 0.0;
for item in numbers.iter() {
mean += *item as f64;
if *item > max {
max = *item;
}
if *item < min {
min = *item;
}
}
mean /= numbers.len() as f64;
assert_eq!(max, 56);
assert_eq!(min, -18);
assert_eq!(mean, 12.5);
println!("Test passed!");
}

輸出結果:

Test passed!

作用域

fn main() {
let planet = "Dunya";
if true {
let planet = "Jupiter";
println!("planet is {}", planet);
}
println!("planet is {}", planet);
}

輸出結果:

planet is Jupiter
planet is Dunya

變量可變性

fn main() {
let car = "Mitsubishi";
println!("car is a {}", car);
// code block, has its own scope
{
// varable shadowing
let car = 1;
println!("car is a {}", car);
}
println!("car is a {}", car);
}

輸出結果:

car is a Mitsubishi
car is a 1
car is a Mitsubishi

棧和堆

fn main() {
println!("=== STACK ====\n");
println!("- values stored in sequential order of insertion");
println!("- data added in LIFO (last in first out)");
println!("- stores variables - pushing values on the stack");
println!("- also holds info for function execution");
println!(
"- stack have very fast access because no guessing where to put data, it will be on top"
);
println!("- stacks are limited in size");
println!("- all data in stack must have a known fixed size\n");
func1();
println!("func1 done");
println!("pop variable y off the stack");
println!("pop variable z off the stack\n");
println!("\n\n=== HEAP ====\n");
println!("- adding data to heap, search for large enough place in memory to store data");
println!("- marks memory spot as being used (allocating) and put data in it");
println!("- accessing data in heap is more complex than the stack because the stack allocates anywhere in available memory");
println!("- slower than stack");
println!("- dynamically add and remove data");
println!("\n\n=== POINTER ====\n");
println!("- data type that stores a memory address");
println!("- pointers have a fixed size so can be stored on the stack");
println!("- adding and accessing data on the heap is done through pointers (addresses in memory)");
}
fn func1() {
println!("func1 executing...");
let y = 3.11;
println!("push variable y = {} onto the stack", y);
let z = 5;
println!("push variable z = {} onto the stack", z);
func2();
println!("func2 done");
println!("pop variable arr off the stack");
}
fn func2() {
println!("func2 executing...");
let arr = [2, 3, 4];
println!("push variable arr = {:?} onto the stack", arr);
}

輸出結果:

=== STACK ====
- values stored in sequential order of insertion
- data added in LIFO (last in first out)
- stores variables - pushing values on the stack
- also holds info for function execution
- stack have very fast access because no guessing where to put data, it will be on top
- stacks are limited in size
- all data in stack must have a known fixed size
func1 executing...
push variable y = 3.11 onto the stack
push variable z = 5 onto the stack
func2 executing...
push variable arr = [2, 3, 4] onto the stack
func2 done
pop variable arr off the stack
func1 done
pop variable y off the stack
pop variable z off the stack
=== HEAP ====
- adding data to heap, search for large enough place in memory to store data
- marks memory spot as being used (allocating) and put data in it
- accessing data in heap is more complex than the stack because the stack allocates anywhere in available memory
- slower than stack
- dynamically add and remove data
=== POINTER ====
- data type that stores a memory address
- pointers have a fixed size so can be stored on the stack
- adding and accessing data on the heap is done through pointers (addresses in memory)

字符串

Rust 有兩種字符串類型。

fn main() {
// Two types of string representation:
// - string literals: hard coded into the executable.
// these are immutable and must be known before compilation
// - String type: allocated data on the heap, \n\tmutable and dynamically generated at runtime
// string literal stored on heap
// String::from() creates a String type from a string literal
// the sequence [m,a,r,s] will get stored on the heap
// to access the string stored on heap, program holds a pointer to it on the stack (message variable)
// that pointer on the stack includes first char memory address, length of string and the capacity so you know how much memory s allocated for it on the heap
let mut message = String::from("Jupiter");
println!("message is {}", message);
// append string to original
// if more memory need than capacity, pointer address updated as well as length and capacity to reflect new location in memory
message.push_str(" is smoke and mirrors");
println!("message is {}", message);
// pushing a char
message.push('!');
println!("message is {}", message);
// get length
println!("message lenght is {}", message.len());
// get capacity in bytes
println!("message capacity is {}", message.capacity());
// check if empty
println!("Is empty: {}", message.is_empty());
// substring search
println!("Contains smoke: {}", message.contains("smoke"));
// replace substring
println!("message is {}", message.replace("smoke","gaz"));
// loop over words in string (split by white space)
for word in message.split_whitespace() {
println!("word is {}", word);
}
// create string with capacity
let mut s = String::with_capacity(4); // 4 bytes capacity
println!("s capacity is  {} bytes", s.capacity());
// 1 byte consumed
// Latin alphabet letters usually have 1 byte size
// remember Unicode supports 4-byte characters
s.push('Q');
s.push('W'); // 1 byte consumed
s.push_str("er"); // 2 bytes consumed
// exceeding string capacity (automagically increased and reallocation in memory)
s.push('T'); // 1 byte consumed
println!("s capacity is  now {} bytes", s.capacity());
}

輸出結果:

message is Jupiter
message is Jupiter is smoke and mirrors
message is Jupiter is smoke and mirrors!
message lenght is 29
message capacity is 56
Is empty: false
Contains smoke: true
message is Jupiter is gaz and mirrors!
word is Jupiter
word is is
word is smoke
word is and
word is mirrors!
s capacity is  4 bytes
s capacity is  now 8 bytes

所有權

fn main() {
/* need to clean up allocated memory blocks no longer needed
in C/C++: malloc() and free() for manual memory mngt
other approach is garbage collection which is automatic */
/*
Rust uses OWNERSHIP ystem:
- variables are responsible for freeing their own resources
- every value is owned by only one variable at a time
- when owning variable goes out of scope the value is dropped
- there are ways to transfer ownership of a value from one variable to another
*/
let outer_planet: String;
let outer_galaxy: String;
let outer_planet_position: i32;
// inner code block scope
{
let inner_planet = String::from("Mercury");
println!("inner_planet is {}", inner_planet);
/*
because ownership mandates only one owner per value/data,
- inner_planet will no longer point to the String value on the heap
- transferring ownership from one variable to another is called a "move" in Rust
- this means that NO shallow copy of data STORED ON THE HEAP in Rust
(shallow copy = several variables pointing to same data in memory)
*/
// transferring ownership
outer_planet = inner_planet;
// can no longer use inner_planet variable after the move of ownership of string data
// println!("inner_planet is {}", inner_planet); // => will panic
let mut inner_galaxy = String::from("Milky Way");
println!("inner_galaxy is {}", inner_galaxy);
// to duplicate data, creates a deep copy of the String data
outer_galaxy = inner_galaxy.clone();
inner_galaxy.clear();
println!("inner_galaxy is now: {}", inner_galaxy);
println!("outer_galaxy is {}", outer_galaxy);
// integer data types live on the stack
let mut inner_planet_position = 1;
println!("inner_planet_position is {}", inner_planet_position);
/*
a copy of the integer data is created for the outer_planet_position
- ownership is respected (no shallow copy - only one variable per value at a time)
- generally STACK-ONLY data types (ie fixed size) are implicitly copied
when variable containing them is assigned to another variable
- data types stored om stack implement the trait that allow them to be copied rather than moved
*/
outer_planet_position = inner_planet_position;
inner_planet_position += 4;
println!("inner_planet_position is {}", inner_planet_position);
println!("outer_planet_position is {}", outer_planet_position);
}
println!("\nouter_planet is {}", outer_planet);
println!("outer_galaxy is {}", outer_galaxy);
println!("outer_planet_position is {}", outer_planet_position);
}

輸出結果:

inner_planet is Mercury
inner_galaxy is Milky Way
inner_galaxy is now:
outer_galaxy is Milky Way
inner_planet_position is 1
inner_planet_position is 5
outer_planet_position is 1

所有權轉移

fn main() {
let rocket_fuel = 1;
process_fuel(rocket_fuel);
println!("rocket_fuel is {}", rocket_fuel);
}
/*
because propellant is i32 so lives on the stack, the value is COPIED jn fn scope
*/
fn process_fuel(mut propellant: i32) {
// the copy is modified
propellant += 2;
println!("Processing propellant {}", propellant);
}

輸出結果:

Processing propellant 3
rocket_fuel is 1
fn main() {
let mut arr_1: [u8; 2] = [33, 66];
// ////////////////
// fixed-length types (stored on the stack) are COPIED
// ////////////////
let arr_2 = arr_1;
println!("arr_1 is {:?}", arr_1);
arr_1 = [1, 2];
println!("arr_1 is now {:?}", arr_1);
println!("arr_2 is {:?}", arr_2);
// ////////////////
// mutable-length type values move the ownership to new variable
// ////////////////
let vec_1 = vec![3, 4];
let vec_2 = vec_1;
// can be no longer use the variable which ownership has been "moved"
// println!("vec_1 is {:?}", vec_1); // => wll panic
println!("vec_2 is {:?}", vec_2);
// to borrow value owned by a variable without moving ownership,
// use a reference to that value
let vec_4 = vec![5, 6, 7];
// borrowing value using a reference (&<NAME>)
let vec_5 = &vec_4;
println!("vec_4 is {:?}", vec_4);
println!("vec_5 is {:?}", vec_5);
}

輸出結果:

arr_1 is [33, 66]
arr_1 is now [1, 2]
arr_2 is [33, 66]
vec_2 is [3, 4]
vec_4 is [5, 6, 7]
vec_5 is [5, 6, 7]

結構體

// used to create custom data type
struct Position {
longitude: f64,
latitude: f64,
}
// tuple struct
struct Signal(u8, bool, String);
struct Car {
model: String,
year: String,
used: bool,
}
// associate functions to struct
impl Car {
// construct car
fn new(m: &str, y: &str) -> Car {
Car {
model: m.to_string(),
year: y.to_string(),
used: false,
}
}
// self is equivalent to "this" is JavaScript
fn serialize(&self) -> String {
format!(
"model: {} - year: {} - used: {}",
self.model, self.year, self.used
)
}
// mutate state
fn marked_used(&mut self) {
self.used = true;
}
}
fn main() {
let mut pos_1 = Position {
latitude: 27.299112,
longitude: 95.387110,
};
println!(
"pos_1 is {:.3}, {:.3}",
pos_1.latitude,
pos_1.longitude
);
pos_1.latitude = 23.1111;
println!(
"pos_1 is now {:.3}, {:.3}",
pos_1.latitude,
pos_1.longitude
);
let mut s1 = Signal(0, true, String::from("ok"));
println!("s1 is {}, {}, {}", s1.0, s1.1, s1.2);
s1.0 = 23;
s1.1 = false;
s1.2 = String::from("NETERR");
println!("s1 is now {}, {}, {}", s1.0, s1.1, s1.2);
let car_1 = Car::new("QBC", "2133");
println!("car_1 is a {} of {}", car_1.model, car_1.year);
let is_used = if car_1.used == true {
"used"
} else {
"brand new"
};
println!("car_1 is {}", is_used);
println!("car_1 is {}", car_1.serialize());
let mut car_2 = Car::new("ZZ7", "2042");
println!("car_2 is a {}", car_2.serialize());
car_2.marked_used();
println!("car_2 is now {}", car_2.serialize());
}

輸出結果:

os_1 is 27.299, 95.387
pos_1 is now 23.111, 95.387
s1 is 0, true, ok
s1 is now 23, false, NETERR
car_1 is a QBC of 2133
car_1 is brand new
car_1 is model: QBC - year: 2133 - used: false
car_2 is a model: ZZ7 - year: 2042 - used: false
car_2 is now model: ZZ7 - year: 2042 - used: true

枚舉

enum Controller {
Turbo,
Up,
Down,
Left,
Right,
X,
Y,
A,
B,
}
fn push_button_notify(c: &Controller) {
// pattern mathing (equivalent to swith in JavaScript)
match c {
Controller::Turbo => println!("Turbo button pushed."),
Controller::Up => println!("Up button pushed."),
Controller::Down => println!("Down button pushed."),
Controller::Left => println!("Left button pushed."),
Controller::Right => println!("Right button pushed."),
Controller::Y => println!("Y button pushed."),
Controller::X => println!("X button pushed."),
Controller::A => println!("A button pushed."),
Controller::B => println!("B button pushed."),
}
}
fn main() {
let secret_push_combo = [
Controller::Up,
Controller::Left,
Controller::A,
Controller::Turbo,
Controller::Y,
Controller::B,
Controller::Turbo,
Controller::Down,
Controller::Right,
Controller::X,
];
for push in secret_push_combo.iter() {
push_button_notify(push);
}
}

輸出結果:

Up button pushed.
Left button pushed.
A button pushed.
Turbo button pushed.
Y button pushed.
B button pushed.
Turbo button pushed.
Down button pushed.
Right button pushed.
X button pushed.

原文鏈接:https://itnext.io/deep-dive-into-rust-for-node-js-developers-5faace6dc71f

聲明:本文由 CSDN 翻譯,轉載請註明來源。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/kaPudEuA7O5a7j5dHRsJ4w