Rust 快速入门(一)

Rust安装信息解释

  • cargo:Rust的编译管理器、包管理器、通用工具。可以用Cargo启动新的项目,构建和运行程序,并管理代码所依赖的所有外部库。

  • Rustc:Rust的编译器。通常Cargo会替我们调用此编译器。

  • Rustdoc:是Rust的文档工具。

基础语法

变量

Rust是强类型语言(Java 、C++ 、Rust 、Go ),但是具有自动判断变量类型的能力。

注意:

  • 强类型语言要求每个变量在声明时必须指定明确的数据类型,并且在程序的整个生命周期中都必须遵循这个类型。语言通常会严格限制不同类型之间的隐式转换,只有明确的、兼容的转换才能进行。这种语言有助于减少由于类型不匹配而导致的错误,并且更容易进行静态检查和优化。

  • 弱类型语言对变量的类型要求相对宽松。程序允许对不同类型的变量进行隐式转换,语言运行时会根据需要自动进行类型转换。这虽然提供了更多的灵活性,但也可能带来一些类型转换带来的潜在错误。

声明变量使用 let 关键字

复制代码
let a = 123;    
let a:u64 = 12;// 带类型的声明

如果上述a被声明了,那么

复制代码
a = "abc"; // a已经是一个int类型
a = 4.56; // a已经是一个int类型
a = 456; // Rust规范,在语言层面尽量少的让变量的值可以改变

上述三个是被禁止的操作,a被称为不可变变量(a的值不变,但是不代表它不是一个变量),这样操作是为了并发安全。

如果我们要声明一个可变变量,只需加一个 **mut**关键字

复制代码
let mut a = 456

那么常量和不可变变量的区别是什么呢?

  • 不可变量如下可编译

    复制代码
    let a = 12;
    let a = 123;

    可编译

    只是有warning。

  • 如果a为常量就不可编译了

    复制代码
    const a: i32 = 123;
    let a = 456;

重影Shadowing

  • 变量的值可以"重新绑定",但在"重新绑定"以前不能私自被改变,这样可以确保在每一次"绑定"之后的区域里编译器可以充分的推理程序逻辑。

  • 这里的重影实际上就是上述"重新绑定"这个概念,重影即指的是变量名称可以被重新使用的机制。

  • 重影与可变变量的赋值不是一个概念,重影是指用同一个名字重新代表另一个变量实体,其类型、可变属性和值都可以变化。但可变变量赋值仅能发生值的变化。

复制代码
fn main() {
    let x = 5;
    let x = x + 1;
    let x = x * 2;
    println!("The value of x is: {}", x);
}

数据类型

注意:

  • Rust不支持 ++ 和 --

  • 在Rust中,方法的调用优先级高于前缀运算符(对负值进行方法调用时一定要先加负号)

    复制代码
    assert_eq!((-32760_i16).saturating_sub(10), -32768);

    如果这里写成-32760_i16.saturating_sub(10)则该断言一定panic

整数类型

位长度 有符号 无符号
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch(机器字) isize usize

isize 和 usize 两种整数类型是用来衡量数据大小的,它们的位长度取决于所运行的目标平台,如果是 32 位架构的处理器将使用 32 位位长度整型。

Rust在调用类型本身的方法之前,必须确切的知道一个值属于哪种整型,只有在解析完所有方法之后类型仍然不明确的时候,才会默认位i32。正确示例如下:

复制代码
 println!("{}", i32::abs(-4));
 println!("{}", (-4_i32).abs());

用于处理溢出的多种整型算术方法:

  1. 检查算法:检查运算会返回结果的 Option 值,如果数学意义上正确的结果可以表示为该类型的值,那么就为Some(v), 否则位None,如:

    复制代码
    assert_eq!(10_u8.checked_add(20), Some(30)); // 可以运行,都处于u8的 0 - 2^8-1(255)范围内
    assert_eq!(100_u8.checked_add(156), None); // 溢出了u8类型,所以会返回 None
  2. 回绕算法:会返回与"数学意义上正确的结果"对"值类型范围"取模的值相等的值,如:

    复制代码
    assert_eq!(100_u16.wrapping_mul(200),20000); // 该行代码可以运行,20000 < u16的65535
    assert_eq!(500_u16.wrapping_mul(500),53392); // 该行代码也可以运行,这是因为250000 > 2^16, 所以会用 250000 % 2^16 最终得到的便是 53392

    需要注意的是对于有符号类型的运算可能会回绕为负值。

  3. 饱和算法:会返回最接近"数学意义上的正确结果"的可表达值(我理解为在计算值res溢出所选类型的最大值时,取所选类型最大值;计算值res所小于类型的最小值时,取所选类型最小值),如:

    复制代码
    assert_eq!(32760_i16.saturating_add(10), 32767);
    assert_eq!((-32760_i16).saturating_sub(10), -32768);

    注意:不存在饱和除法,饱和求余法或饱和位移法

  4. 溢出算法:返回一个元组(result, overflow), result是回绕版本所返回的内容,而overflowed是一个布尔值,指示是否发生过溢出,如:

    复制代码
    assert_eq!(255_u8.overflowing_sub(2), (253, false)); // 未溢出
    assert_eq!(255_u8.overflowing_add(2), (1, true)); // 溢出,返回回绕方法结果 1

浮点数类型

Rust提供了 IEEE 单精度浮点类型和 IEEE 双精度浮点类型。

复制代码
let x = 2.0; // f64
let y: f32 = 3.0; // f32

浮点类型字面量:

浮点数中整数部分之后的每个部分都是可选的,但是必须存在浮点数部分,指数部分,类型后缀中的一项。

小数部分也可以由单独.组成,例如 5. 是一个浮点常量。

Rust会将整型字面量和浮点类型字面量视为不同的大类,即它永远不会把整型字面量推断为浮点类型。

一些特殊值关联常量

  • INFINITY:无穷大

  • NEG_INFINITY:负无穷大

  • NAN:非数值

  • MIN:最小有限值

  • MAX:最大有限值

使用示例

复制代码
assert((-1./f32::INFINITY).is_sign_negative())

布尔类型

true,false

if和while这样的控制结构,他们的条件必须时bool表达式,Rust的as运算符可以将bool转化为整型:

复制代码
assert_eq!(false as i32, 0)
assert_eq!(true as i32, 1)

字符类型

char

Rust的 char 类型大小为 4 个字节(32位),代表 Unicode标量值(Java 2 字节, c/c++ 1字节)

Rust会对单独的字符使用char类型,但是对于字符串和文本流使用UTF-8编码。(所以String为UTF-8字符序列,而不是类似于Java中的字符数组)

转换

  • 在Rust中字符被视为与数值截然不同的类型:char既不是u8,也不是u32(尽管他确实有32位长)

  • 尽管数值类型和char是不同的,但是Rust位u8值提供了字节字面量,如:b'X'表示以字符X的ASCII作为u8值

  • Rust不会再char和任何其他类型之间进行隐式转换。可以使用as转换运算符将char转换为整型,对于小于32位的类型,该字符值的高位会被截断。

  • u8是唯一能够通过as运算符转换为char的类型,因为Rust刻意让as运算符只执行开销极低且可靠的转换。

复合类型

  • 元组:一对 ( ) 包括的一组数据,可以包含不同种类的数据

    复制代码
    let tup: (i32, f64, u8) = (500, 6.4, 1);
    // tup.0 等于 500
    // tup.1 等于 6.4
    // tup.2 等于 1

    Rust代码通常会使用元组类型从一个函数返回多个值(这里很像golang原生提供的多个返回值的特性),如:

    复制代码
    let text = "我是米卫兵,我喜欢完原神,我也喜欢玩星穹铁道";
    let(head, tail) = text.split_at(15);
    println!("{}", head);
    println!("{}", tail);

    零元组:又被称为 单元类型 ,用于:当无法携带任何有意义的值,但是其上下文仍然要求传入某种类型时。如:

    复制代码
    fn swap<T>(x: &mut T, y: &mut T);
    // 上者完整写法如下:
    fn swap<T>(x: &mut T, y: &mut T) -> ();

    还有一个点,需要注意:在使用单个值的元组的时候一定要追加尾逗号 ("hello",),如此一来便可区分普通括号表达式和元组。

  • 数组,和go类似,但是需要注意的几点如下

    复制代码
    let d = [3; 5]; // 这样声明数组等效于 let d = [3, 3, 3, 3, 3];
    • 然后就是 mut 这个关键字,与不可变变量概念类似,只有一个数组声明了mut后,其中元素才可以被修改。

区别:

  • 元组中每个元素都可以有不同的类型,而数组中所有元素必须是相同的类型。

  • 元组只允许使用常量作为索引。如:t.1, t.2 而不能使用 t.i

指针类型

不同于大多数具有垃圾回收机制的语言,Rust会将内存分配保持在最低限度。默认情况下会嵌套,三种指针类型:

引用

&String => 读作 "ref String" 是对String类型的引用。表达式 &x 会生成一个对x的引用,在Rust

术语中,我们会说它借用了对x的引用 。给定一个引用r,表达式 *r会引用r指向的值。

  • 与c/cpp类似:

    • 当超出作用域时,引用不会自动释放任何资源。
  • 与c/cpp不同:

    • Rust的引用永远不位空。

    • Rust会跟踪值的所有权和生命周期。

如此一来,Rust在编译期就可以排除悬空指针、双重释放和指针失效等错误。

Rust引用两种形式
  • &T

    • 一个不可变共享引用。
  • mut T

    • 一个可变的、独占引用。

Box

堆种分配内存最简单的方式:Box::new,如:

复制代码
let t = (12, "eggs");
let b = Box::new(t)
// t类型为(i32, &str) => b类型为Box<i32, &str>

当b超出作用域时,内存会立即被释放,除非b已经被移动(move)。

不安全指针(裸指针)

  • *mut T

  • *const T

相关推荐
Pniubi11 分钟前
Java为什么要分堆和栈
java·开发语言
愚农搬码26 分钟前
自研表单引擎、开源组件二开和商业产品,企业应该如何选择?
后端
用户2986985301436 分钟前
Python 自动化处理 Word 页眉页脚的几种典型场景
后端·python·api
小小晓.1 小时前
Qt 对话框、QLineEdit 与布局管理实战详解
开发语言·qt
愚农搬码1 小时前
一款企业级电子表单系统应该具备哪些功能?
后端
炸薯条!1 小时前
博客系统测试报告
开发语言·lua
Evand J1 小时前
【MATLAB例程12】永磁同步电机基于增广无迹卡尔曼滤波的未知负载转矩在线观测
开发语言·matlab
Rain的Java大神之路1 小时前
国家数据集团校招、社招都开了:总部雄安,这些技术岗值得认真看看
java·后端·面试
XW2 小时前
Agent 上下文管理实现详解:从窗口工程到长期记忆
后端
Moment2 小时前
为什么这么多 Agent Harness 都选择 TypeScript
前端·后端·面试