4

Node.js 开发者的 Rust 入门指南

 2 years ago
source link: https://www.fly63.com/article/detial/11080
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
更新日期: 2022-01-28阅读量: 27标签: node分享

扫一扫分享

随着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,因此编程难度并不高。

1、创建一个Rust项目

本文所有的代码都可以利用 Rust playground 在线运行(当然,除了那些需要访问本地的代码之外):https://play.rust-lang.org/。

在安装好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中,行尾必须使用分号(;),除非是函数最后一行的返回语句(稍后进一步解释)。

3、对数值输出进行高级格式化

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
    // stored on the heap (more on that later)
    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

5、基本类型

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 = i32::MAX;
    let max_i16 = 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 = ':smiling_imp:';
    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 :smiling_imp: - type: char

6、浮点数

f32 (32位浮点数)

f64 (64位浮点数)

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 = f32::MIN;
    println!("min_f32 is: {}\n", min_f32);
    let max_f32 = f32::MAX;
    println!("max_f32 is: {}\n", max_f32);
    let min_f64 = f64::MIN;
    println!("min_f64 is: {}\n", min_f64);
    let max_f64 = 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

7、位操作(高级内容,可以跳过)

/*
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

8、布尔和二进制代数

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

9、算术操作

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

10、比较操作

/*
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

11、字符

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 ☝

12、计算平均值

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.

13、数组

fn main() {
    // fixed length and single typed
    // stored in contiguous memory locations
    // Contiguous means that elements are laid out so that every element is the same distance from its neighbors.
    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]

14、slice

fn main() {
    // slice: used to reference a contiguous section (subset) of a collection (aarray, etc.) WITHOUT taking ownership of these elements
    // commonly used: string slice dt type : &str
    // string literals are slices
    // the data is hard-coded in the executable binary and the program  uses a string slice to access it
   // create a string slice from string data
    // the sentence variable has a pointer to the beginning of the string data in memory as well, as info about its length and capacity
    let sentence = String::from("This is a sequence of words.");
    println!("sentence is {}", sentence);
    // the last_word variable has a pointer to the offset/start index of the section of the string data, as well as the length of the slice
    // as usual, the end index of the slice is excluded from the result (common in most programming languages when slicing collections)
    let last_word = &sentence[22..22 + 5];          // [start..end_excluded]
    println!("last_word is \"{}\"", last_word);
    // slice from offset index to end of the collection (here string data)
    let last_part: &str = &sentence[22..];
    println!("last_part is \"{}\"", last_part);
    // slice from beginning of collection up until end index
    let without_last_word = &sentence[..22];
    println!("without_last_word is \"{}\"", without_last_word);
    // the length of a string slice is in number of bytes (usize data type)
    // NOT in number of characters
    let slice_length: usize = last_part.len();
    println!("slice_length is {} bytes", slice_length);
    // when creating a string slice, the range indices must occur at valid UTF-8 character boundaries
    // remember that UTF-8 characters can occupy multiple bytes
    // all this to say that slice range indices must be character boundaries
    // if you index in the middle of a character, the program will panic
    // so be careful when creating string slices from strings with special characters or emojis
   // yes, this is some low-level stuff that we usually don't deal with in everyday Node.js
}

输出结果:

sentence is This is a sequence of words.
last_word is "words"
last_part is "words."
without_last_word is "This is a sequence of "
slice_length is 6 bytes

15、slice 作为参数

fn main() {
    let message = String::from("lorem ipsum");
    let first_word = get_first_word(&message);
    println!("first_word is \"{}\"", first_word);
    let first_word_too = get_first_word_too(&message[6..]);
    println!("first_word_too is \"{}\"", first_word_too);
}
// ======= passing the entire string as input ===========
fn get_first_word(msg: &String) -> &str {
    // create a slice of bytes (&[u8] data type) from string data
    let bytes: &[u8] = msg.as_bytes();
    // iterate through byte sequence one byte at a time
    // use enumerate() to get the index when iterating
   for (index, &item) in bytes.iter().enumerate() {
        // find first space and return everything before as a string slice
        // b' ' is the byte representation of a blank space
        // remember that we are iterating on a sequence of bytes, NOT characters
       // we do that because the index for a string slice is in terms of bytes
       if item == b' ' {
            return &msg[..index];
       }
    }
    // no blank space found, return entire message
    &msg
}
// ======= passing a string slice as input ===========
fn get_first_word_too(msg: &str) -> &str {
    let bytes: &[u8] = msg.as_bytes();
    for (index, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &msg[..index];
        }
    }
    &msg
}
first_word is "lorem"
first_word_too is "ipsum"

关于 Deref coercion:

传递对字符串的借用引用 (&String)与传递字符串切片 (&str) 不同

借用字符串引用指向堆栈上的字符串,该字符串又拥有并指向堆上的数据

切片仅存储指向堆数据的指针以及长度信息。它不包含容量,因为它不拥有堆上的任何东西。

由于字符串引用包含用作切片的所有信息(指向堆数据的指针 + 长度),Rust 允许在需要字符串切片的地方使用字符串引用,这个便利性就是 deref coercion

fn main() {
    let message = String::from("lorem ipsum");
   
    // notice that a string reference is passed as argument
    let first_word = get_first_word(&message); 
    println!("first_word is \"{}\"", first_word);
}
// notice that the expected argument is of type string slice (&str)
fn get_first_word(msg: &str) -> &str {
    let bytes: &[u8] = msg.as_bytes();
    for (index, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &msg[..index];
        }
    }
    &msg
}

当然,当需要字符串引用,而使用字符串切片时,deref coercion 不起作用(因为缺少属性)

编写代码时,在不需要数据所有权的情况下更喜欢使用字符串切片,即大家更喜欢使用 &str

16、多维数组

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

17、向量

fn main() {
    // collection of elements with the same data type
    // elements are sorted in order
    // arrays have a fixed size that must be known at compile time
    // because array data is stored on the stack
    // vectors can dynamically grow and shrink
    // by adding / removing items
    // vector data is stored in heap memory
    // therefore you need to handle ownership and borrowing
    // 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]

18、元组

fn main() {
    // used to group related items of mixed data types
    // 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

19、函数

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

20、语句和表达式

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

21、函数返回类型

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 ()

22、闭包

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));
}

输出结果:

23、摄氏度到华氏度转换

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)
}

24、条件执行

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

25、多重条件(if/else if)

n 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

26、循环赋值

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

27、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

28、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>)
    // Adding the & (borrow operator) allows you to 
    // borrow the variable without 
    // taking ownership (see borrowing section) 
    // - 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

29、嵌套循环

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

30、猜数游戏

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 - more on this later)
    // 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);
}

31、统计基础

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!

32、作用域

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

输出结果:

planet is Jupiter
planet is Dunya

33、量可变性

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

34、栈和堆

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)

35、字符串

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

36、所有权

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 stored on the heap, 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

37、所有权转移

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 passed as argument is COPIED jn fn scope
    - to be able to modify the copy inside the function scope, use the mut keyword
*/
fn process_fuel(mut propellant: i32) {
    // the copy is modified
    propellant += 2;
    println!("Processing propellant {}", propellant);
}

输出结果:

Processing propellant 3
rocket_fuel is 1

38、结构体

用于对混合数据类型的多个相关项进行分组

元素被命名(不像它们被排序的元组)

两种结构体(regular struct 和 tuple struct)

// tuple struct
// used to store a collection of mixed data without named fields
// used to be distinguished as a specific type 
// (not just a regular tuple)
struct Signal(u8, bool, String);

// regular struct
// struct names are capitalized 
// like classes in JavaScript and OOP generally
struct Car {
    // fields of the struct
    model: String,
    year: String,
    used: bool,
}
// method: functions/subroutines associated to a struct
// methods are defined within the context of a struct
// the first parameter of a method is the reference to a struct instance
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;
    }
}
struct Position {
  latitude: f64,
  longitude: f64
}
fn print_signal(s: &Signal) {
    println!("s1 is {}, {}, {}", s.0, s.1, s.2);
}
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"));
    // fields of a tuple struct are accessed like regular tuples values
    // using their index
    // remember tuple structs do not have named fields
    print_signal(&s1);
    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());
}

输出结果:

pos_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

39、枚举

// defines a data type with multiple possible variants
enum Controller {
    Turbo,
    Up,
    Down,
    Left,
    Right,
    X,
    Y,
    A,
    B,
}
fn push_button_notify(c: &Controller) {
    // pattern matching (equivalent to switch 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

站长推荐

1.云服务推荐: 国内主流云服务商/虚拟空间,各类云产品的最新活动,优惠券领取。领取地址:阿里云腾讯云硅云

链接: https://www.fly63.com/article/detial/11080


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK