随着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 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中,行尾必须使用分号(;),除非是函数最后一行的返回语句(稍后进一步解释)。
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
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
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
/*
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
// 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]
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
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"
传递对字符串的借用引用 (&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
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() {
// 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]
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
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));
}
输出结果:
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
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
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
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
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
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 - 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);
}
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 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
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
用于对混合数据类型的多个相关项进行分组
元素被命名(不像它们被排序的元组)
两种结构体(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
// 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
关于 Node.js 里 ES6 Modules 的一次更新说明,总结来说:CommonJS 与 ES6 Modules 之间的关键不同在于代码什么时候知道一个模块的结构和使用它。
在这个教程中,我们会开发一个命令行应用,它可以接收一个 CSV 格式的用户信息文件,教程的内容大纲:“Hello,World”,处理命令行参数,运行时的用户输入,异步网络会话,美化控制台的输出,封装成 shell 命令,JavaScript 之外
首先你需要生成https证书,可以去付费的网站购买或者找一些免费的网站,可能会是key或者crt或者pem结尾的。不同格式之间可以通过OpenSSL转换
nodej项目在微信环境开发,nodejs的异步特效,会导致请求没有完成就执行下面的代码,出现错误。经过多方查找,可以使用async模块来异步转同步,只有前一个function执行callback,下一个才会执行。
3G的大文件分1500个2M二进度文件,通post方法发送给node服务,服务器全部接收到文件后,进组装生成你上文件。
JavaScript比C的开发门槛要低,尽管服务器端JavaScript存在已经很多年了,但是后端部分一直没有市场,JavaScript在浏览器中有广泛的事件驱动方面的应用,考虑到高性能、符合事件驱动、没有历史包袱这3个主要原因,JavaScript成为了Node的实现语言。
node.js的第一个基本论点是I / O的性能消耗是很昂贵。因此,使用当前编程技术的最大浪费来自于等待I / O完成。有几种方法可以处理性能影响
在前后端分离的开发中,通过 Restful API 进行数据交互时,如果没有对 API 进行保护,那么别人就可以很容易地获取并调用这些 API 进行操作。那么服务器端要如何进行鉴权呢?
我们经常跟Node.js打交道,即使你是一名前端开发人员 -- npm脚本,webpack配置,gulp任务,程序打包 或 运行测试等。即使你真的不需要深入理解这些任务,但有时候你会感到困惑,会因为缺少Node.js的一些核心概念而以非常奇怪的方式来编码。
运行在 Node.js 之上的 Webpack 是单线程模型的,也就是说 Webpack 需要处理的任务需要一件件挨着做,不能多个事情一起做。happypack把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!