Rust是静态类型的语言,在编译时,必须明确数据的类型。当然,编译器可以根据其值和使用方式,自动推导出数据的类型,不一定需要人工的指定。只有当多种类型都有可能时,需要显式的指定类型。
rust使用let关键字绑定(不是声明)变量,语法为:
let x = 5; // 绑定x变量为5,不指定x的类型
let y:i64 = 10; // 绑定y变量为10,指定y的类型为64位整型
rust的数据类型为分两大类:标量类型与复合类型。标量类型是一个单独的值,而复合类型则是由多个标量类型组合在一起的类型。
标量( scalar) 类型代表一个单独的值。 Rust 有四种基本的标量类型:
rust的整型,根据长度的不同,分为6种,每种又有有符号和无符号之分,如下表:
有符号 | 无符号 | 长度 |
---|---|---|
i8 | u8 | 8bit |
i16 | u16 | 16bit |
i32 | u32 | 32bit |
i64 | u64 | 64bit |
i128 | u128 | 128bit |
isize | usize | 视CPU架构而定,32位CPU为32bit,64位CPU为64bit |
有符号的整型,根据其位数,值域范围为-(2n-1)2<sup>n-1</sup>-1。无符号的整型,根据其位数,值域范围为02n-1。
rust绑定整型变量可以使用下面的多种方式:
let a = 123; // 使用十进制
let b = 123_456; // 为增加可读性,可以使用下划线分隔
let c = 0xff; // 使用16进制
let d = 0o123; // 使用8进制
let e = 0b1101_0011; // 使用二进制
let f = 57u8; // 增加u8后缀,指定其为8位无符号整型
因为现在的CPU架构,32位的整型运算速度要高于64位,所以rust编译器会默认将上面除了f以外其他的都推导为i32,如有特殊需要,可人为指定其他的整数类型。
Rust中的浮点型就不像整型那么多了,它只有f32和f64两种,分别占用32位和64位。现代的CPU中,这两种类型的计算速度几乎一样,并且f64精度更高,所以Rust编译器会默认将类型推导为f64。
let x = 2.0; // f64
let y: f32 = 3.0; // f32
Rust的bool型,只能为true和false。与C/C++不同,其bool类型不能与整型混用。
let b = true;
如果将整型赋值给bool型,编译后报错,如:
let b:bool = 1;
编译时会产生错误:
error[E0308]: mismatched types
--> src\main.rs:2:18
|
2 | let t:bool = 1;
| ^ expected bool, found integer
|
= note: expected type `bool`
found type `{integer}`
error: aborting due to previous error
Rust的字符类型关键字为char,但它与C/C++的char截然不同。Rust的char占用4个字节,可以表示任意Unicode字符,包括英文、中文、日文、韩文,甚至emoji。
let en = 'z';
let ch1 = '中';
let ch2 = '發';
Rust的复合类型是由多个标量类型组成的复杂类型,原生的复合类型有数组的元组两种,在其基础上,又有了切片和字符串。
Rust的数组是由多个同一类型的值组成的复合类型。它与C/C++的数组差不多,一旦声明后就是定长的,不能改变其长度。
let a = [1, 2, 3, 4, 5];
当需要显式的指定元素类型和长度时,可以使用下面的方式:
let a: [i32; 5] = [1, 2, 3, 4, 5];
要访问数组的元素,Rust也是采用下标的方式,如:
fn main() {
let a = [1, 2, 3, 4, 5];
println!("{}", a[3]);
}
Rust的元组可以由多个不同类型的值组成,其长度也不能改变。
let tup: (i32, f64, u8) = (500, 6.4, 1);
元组的访问与数组不同,它不是采用下标的方式,而是采用如下方式:
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
println!("{}", tup.1);
}
这种方式不能动态的计算。
通过下面的方式,可以将元组嵌套入数组中,如:
fn main() {
let a:[(i32, f32);2] = [
(100, 1.1),
(200, 2.2)
];
println!("{}", a[0].1);
}
在Rust中,字符串被分为两种,&str和String。
fn main() {
let a = "Hello"; // 使用""推导出来的类型为&str
let mut b = a.to_string(); // b的类型为String
b += " world."; // b是可变的
println!("a = {:?}, b = {:?}", a, b);
}
&str为字符串字面值,它是不可变的,要对其操作时,首先要转成String类型进行操作。要构造String类型,可以通过如下的方式:
fn main() {
let a = "Hello world".to_string();
let b = String::from("Hello world");
println!("a = {:?}, b = {:?}", a, b);
}
Rust中的字符串,不像其他语言那样是字符型的数组。它不能通过下标访问,如下面的代码:
fn main() {
let a = "Hello world";
println!("{}", a[0]);
}
编译时会报错:
error[E0277]: the type `str` cannot be indexed by `{integer}`
--> src\main.rs:3:20
|
3 | println!("{}", a[0]);
| ^^^^ string indices are ranges of `usize`
|
= help: the trait `std::slice::SliceIndex<str>` is not implemented for `{integer}`
= note: you can use `.chars().nth()` or `.bytes().nth()`
see chapter in The Book <https://doc.rust-lang.org/book/ch08-02-strings.html#indexing-into-strings>
= note: required because of the requirements on the impl of `std::ops::Index<{integer}>` for `str`
这个例子还充分体现出Rust的编译器的人性化,它不但告诉我们出现了什么错误,同时还告诉了解决方法,通过chars()或bytes()转换成数组,就可以了:
fn main() {
let a = "Hello world";
println!("{:?}", a.chars().nth(0));
}
还有一种方式可以将字符串转化为数组:
fn main() {
let a = "Hello world.".to_string();
let b = a.as_bytes();
println!("b = {:?}", b[0]);
}
这种方式是将字符串转化为byte类型的数组,如果是英文的字符串,这没有什么问题,如果字符串里包含中文,会引发问题。
Rust在数组的基础上,构造了切片,从根本上来讲,切片是对数组的引用。
fn main() {
let a = [1,2,3,4,5,6,7,8,9,0];
let s = &a[2..8];
println!("{}", s[0]);
}
s为a数组的切片,切片引用了a数组,并指定引用为从a数组的第2个元素到第8个。
Rust有两种切片方式:
注:start和end都可以省略,如果省略start,则从0开始切片,如果省略end,则一直切到最后一位。
fn main() {
let a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let s1 = &a[2..8];
println!("{}", s1.len()); // 输出结果为6
let s2 = &a[2..=8];
println!("{}", s2.len()); // 输出结果为7
}
对于字符串,同样可以使用切片操作,如:
fn main() {
let a = "hello";
let s = &a[..3];
println!("{}", s);
}
Rust中的元类型为(),相当于c、java中的void。
在学习Rust的过程中,我们看到所有的教程中都在使用绑定,而不是声明,我没有查询到绑定与声明的区别到底在哪里,但从语法上看,绑定比起声明还是高级一些的。
变量绑定使用let关键字,其实,上面的代码中我们已经接触到了很多种绑定的方式,如:
fn main()
{
let a = 1; // 自动推导变量类型
let b:i64 = 1; // 使用类型注解指定变量类型
let c = 1i64; // 使用后缀指定变量类型
let d = 1_i64; // 使用后缀指定变量类型的另一种方式
}
变量绑定时,还可以使用一种高级的方式,叫模式匹配,模式匹配是Rust的另一精华所在。有很多种语言也支持模式匹配,但Rust将其发挥到的极致。
复杂的模式匹配后面会拿出一篇单独学习,这里看一下简单的:
fn main()
{
let (a, b):(i32, i64) = (1, 3);
}
通过这种方式可以同时绑定多个变量,并分别指定其类型。
和C/C++一样,Rust的变量在使用前必须要进行初始化,如果没有初始化,C/C++会出现core dump错误,而Rust的编译器会为我们进行检查,直接不能通过编译。
如果我们绑定一个没有初始化的变量,但并不使用它,如:
fn main()
{
let a:i32;
}
编译时会有警告:
warning: unused variable: `a`
--> src\main.rs:3:9
|
3 | let a:i32;
| ^ help: consider prefixing with an underscore: `_a`
|
= note: `#[warn(unused_variables)]` on by default
而一旦使用它,编译器就会严厉的拒绝,如:
fn main()
{
let a:i32;
println!("{}", a);
}
编译时直接出错,使用了未初始化的变量:
error[E0381]: borrow of possibly-uninitialized variable: `a`
--> src\main.rs:4:20
|
4 | println!("{}", a);
| ^ use of possibly-uninitialized `a`
error: aborting due to previous error
Rust与众不同之处,是其变量也具有可变性。let绑定变量,默认为不可变变量,如:
fn main()
{
let a:i32 = 1;
a = 2;
println!("{}", a);
}
编译会报错:
error[E0384]: cannot assign twice to immutable variable `a`
--> src\main.rs:4:5
|
3 | let a:i32 = 1;
| -
| |
| first assignment to `a`
| help: make this binding mutable: `mut a`
4 | a = 2;
| ^^^^^ cannot assign twice to immutable variable
error: aborting due to previous error
编译器告诉我们要使用mut关键字,于是我们可以修改代码为:
fn main()
{
let mut a:i32 = 1;
a = 2;
println!("{}", a);
}
可以通过编译。
相信很多人会问:“不可变的变量,不就是常量吗?”,答案是否定的。Rust可以用const关键字定义常量,常量与变量存在如下区别:
fn main()
{
const mut a = 1;
}
编译会出错:error: const globals cannot be mutable
--> src\main.rs:3:11
|
3 | const mut a = 1;
| ----- ^^^ cannot be mutable
| |
| help: you might want to declare a static instead: `static`
fn main()
{
const a = 1;
}
还是会出错:error: missing type for `const` item
--> src\main.rs:3:11
|
3 | const a = 1;
| ^ help: provide a type for the item: `a: i32`
error: aborting due to previous error
let A:i32 = 1;
fn main()
{
println!("{}", A);
}
编译出错:error: expected item, found keyword `let`
--> src\main.rs:1:1
|
1 | let A:i32 = 1;
| ^^^ expected item
error: aborting due to previous error
将上面代码中的let替换成const,则可以编译通过。尽管全局变量不是很安全,但在很多场合下还是需要全局变量的。Rust特别注重代码的安全性,所以Rust没有全局变量。想要达到这个效果,可以使用静态变量代替,如:
static a:i32 = 1;
fn main()
{
println!("{}", a);
}
static同样默认为不可变的,不可变的静态变量不会引发安全问题,但要让静态常量可变,编译器就不同意了:
static mut a:i32 = 1;
fn main()
{
a = 10;
println!("{}", a);
}
编译器立马亮起红灯:
error[E0133]: use of mutable static is unsafe and requires unsafe function or block
--> src\main.rs:5:5
|
5 | a = 10;
| ^^^^^^ use of mutable static
|
= note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior
error[E0133]: use of mutable static is unsafe and requires unsafe function or block
意思是可变的静态变量在多线程中是不安全的,要想使用可变的静态变量,你必须使用unsafe函数或代码块。
所以把上面的代码修改为:
static mut a:i32 = 1;
fn main()
{
unsafe
{
a = 10;
println!("{}", a);
}
}
终于可以通过编译了。
前面的代码中,虽然没有特别指出,但我们不可避免的用到了Rust的函数与宏。相信很多初学者都会提出疑问,为什么println后面还会有个感叹号。是的,我们的main函数是函数,而println就是宏。
Rust使用fn关键字定义函数,其语法为:
fn 函数名(参数列表)-> 返回值类型
如:
fn main()
{
let sum = add(1, 2);
println!("{}", sum);
}
fn add(a:i32, b:i32) ->i32
{
return a + b;
}
Rust的函数不需要像C语言可样,被使用的函数必须要在使用之前声明,它可以在代码的任何地方定义。
好了,Rust的函数就讲到这里。
等,等下,这就完了吗?按照惯例,这里还要讲解默认参数和可变参数啊。
抱歉,我没有找到在Rust中使用默认参数和可变参数的方法,如果你知道了,麻烦您告诉我,谢谢。
Rust中宏的功能非常强大,从根本上来说,宏是一种为写其他代码而写代码的方式,即所谓的元编程( metaprogramming)。用白话说,Rust的宏与C/C++的宏是一样的,在编译前,编译器会将宏进行展开,生成新的代码,然后对新的代码进行编译。
Rust宏的应用,除了刚才见到的println之外,还有非常实用的动态数组。如:
fn main()
{
let mut a = vec![0, 1, 2];
a.push(3);
println!("{}", a.len());
}
我们可以自定义宏,但那不再属于基础知识,我目前还看不太懂,只好放到后面学习了。
Rust 是一门基于表达式( expression-based)的语言,这是一个不同于其他语言重要区别。只有两种语句(“声明语句”和“表达式语句”),其它的一切都是表达式。
语句( Statements)是执行一些操作但不返回值的指令。Rust只有两种语句:
表达式( Expressions)可以计算并产生一个值,概念上类似于C/C++的右值。如声明语句是语句而不是表达式,它不能作为右值。所以let a = (let b = 5);是不合法的。而下面的代码则是合法的:
let y = {
let x = 3;
x + 1
};
x + 1,因为它后面没有加分号,所以它是表达式,而不是表达式语句。它可以作为右值被绑定到变量。
回到刚才自定义函数的例子,我们也可以省略return和分号,写成下面这样:
fn main()
{
let sum = add(1, 2);
println!("{}", sum);
}
fn add(a:i32, b:i32) ->i32
{
a + b
}
a + b是一个表达式,可以直接被add函数返回,作为右值绑定到sum。这也充分说明,Rust不是以分号作为行的结束。相反,如果这一行加上分号,表达式变成了表达式语句,这种写法是不合法的。
fn main()
{
let sum = add(1, 2);
println!("{}", sum);
}
fn add(a:i32, b:i32) ->i32
{
a + b;
}
此时编译出错:
error[E0308]: mismatched types
--> src\main.rs:7:24
|
7 | fn add(a:i32, b:i32) ->i32
| --- ^^^ expected i32, found ()
| |
| implicitly returns `()` as its body has no tail or `return` expression
8 | {
9 | a + b;
| - help: consider removing this semicolon
|
= note: expected type `i32`
found type `()`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
因为add函数没有返回值,编译器认为它返回了元类型(),与定义中指定的返回i32不一致。