HelloWorld
|
|
! 表示这是一个宏(macro),而非一个函数(function)
|
|
Installation
|
|
|
|
编辑 $HOME/.cargo/config
|
|
Cargo
包管理类似pip npm 但还提供一些工作流 创建新项目:cargo new
编译:cargo build
运行:cargo run
更新项目依赖:cargo update
执行测试:cargo test
生成文档:cargo doc
静态检查:cargo check
|
|
Cargo.toml 是工程的描述文件,包含 Cargo 所需的所有元信息。
|
|
在Rust中,依赖的代码包被称为crates https://crates.io/ src 放置源代码, 约定:main.rs / lib.rs 是入口文件。--lib
可以生成库项目
运行 cargo run 或 cargo build,可执行文件将生成在 target/debug/ 目录,运行 cargo build –release,可执行文件将生成在 target/release/
Basic
变量
Rust 中变量默认是不可变的(immutable),使用 mut 标志为可变(mutable) 静态类型语言 支持类型推导
|
|
不需要追踪一个不可变值如何和在哪可能会被改变
常量
常量用const声明,只能被设置为常量表达式,而不能是函数调用的结果,一般会被编译器内联优化而不分配内存空间。
|
|
函数
|
|
基本数据类型
- 布尔值(bool)
- 字符(char) // Unicode
- 有符号整型(i8, i16, i32, i64, i128)
- 无符号整型(u8, u16, u32, u64, u128)
- 浮点数(f32, f64)
- 数组(arrays),由相同类型元素构成,长度固定。
|
|
动态数组可以用vector,会在堆上分配空间。
- 元组(tuples),由相同/不同类型元素构成,长度固定。
|
|
使用:a.1
运算符
|
|
- 比较运算符 == != < > <= >=
- 逻辑运算符 ! && ||
- 位运算符 & | ^ « »
- 赋值运算符
|
|
- 类型转换
|
|
控制流
- if else else if
|
|
- match
|
|
- loop // 类似while(1)
|
|
- while & for
|
|
Guessing game
- 尝试
$ cargo new guessing_game
$ cd guessing_game
|
|
- 引入外部crate https://crates.io/crates/rand
|
|
cargo build
- 生成随机数
|
|
- 比较结果
|
|
Ordering是一个枚举,它的成员是 Less、Greater 和 Equal。这是比较两个值时可能出现的三种结果。 Rust 允许用一个新值来隐藏guess 之前的值。这个功能常用在需要转换值类型之类的场景。它允许我们复用guess变量的名字,而不是被迫创建两个不同变量,诸如 guess_str 和 guess 之类。
- 使用循环
|
|
- 处理无效输入
|
|
将 expect 调用换成 match 语句,这样不会遇到错误时就崩溃,parse 返回一个 Result 类型,而 Result 是一个拥有 Ok 或 Err 成员的枚举。这里使用的 match 表达式,和之前处理 cmp 方法返回 Ordering 时用的一样。 关于枚举与错误处理,我们后面详细讲到
- 最终版
|
|
结构体与面向对象范式
|
|
关联函数(静态方法) 通常构造函数用此来实现
与Go对比
|
|
元组结构体
在参数个数较少时,无字段名称,仅靠下标也有很强的语义时,为每个字段命名就显得多余了。例如:
|
|
trait与泛型
用于接口抽象与泛型约束
|
|
泛型
|
|
枚举与错误处理
无参数枚举体
|
|
类C枚举体
|
|
带参数枚举体
|
|
使用泛型+枚举的两个示例
Option是标准库中定义的一个非常重要的枚举类型。它表示值的可能性。对Rust而言,变量在使用前必须要赋予一个有效的值,所以不存在空值(Null),如果一个值可能为空,需要显式地使用Option来表示。
|
|
unwarp可以取出some值,但如果是None会panic,实际是match的语法糖。
expect遇到none值会显示指定的异常消息。
|
|
Rust中的错误处理是通过返回Result<T, E>
枚举进行的,它表示错误的可能性。
|
|
有两个泛型类型T E,它表达成功或失败 用于错误处理 将Result<T, E>作为函数返回值,强制开发者处理OK 和 Err两种类型。
|
|
Rust内存管理机制
按照内存管理方式可将编程语言大致分为两类:手动内存管理类和自动内存管理类。 前者需要开发者手动使用malloc和free等函数显式管理内存,如C。后者使用GC(Garbage Collection, 垃圾回收)来对内存进行自动化管理,如GO, Java。
前者优势性能高,但容易写出内存安全问题的程序,如空指针、悬垂指针、double free等。 后者使用GC接管了开发者管理内存的任务,但为了安全引入了性能负担,GC的时候会引起“世界暂停”,不能作为系统级编程语言(GC是建立在虚拟内存抽象上的)。
有木有一门语言将两者性能结合起来? Rust推崇安全与速度至上,它没有垃圾回收机制,却成功实现了内存安全 (memory safety)。
通过设计一道合理的机制,将内存安全交给编译器解决,没有GC,内存由编译器分配,Rust编译为LLVM IR 其中携带了内存的分配信息,在编译时就可以确定何时需要分配、释放内存。
内存泄漏不在内存安全范围内
Ownership
C语言使用内存
|
|
为缓解此问题C++引入了智能指针,即通过智能指针来描述所有权,实现了内存的半自动化管理。
|
|
使用变量的生命周期绑定资源的使用周期,这种资源管理的方式有一个术语叫RAII(Resource Acquisition Is Initialization),智能指针就是基于RAII机制来实现的。 在现代C++中RAII的机制是使用构造函数来初始化资源,使用析构函数来回收资源(在栈对象析构函数中释放堆内存)。
存在安全隐患的示例:
|
|
空指针解引 segmehtation fault 语义上的 move 并没有静态检查
Rust在此基础上更进一步,将所有权的概念融入到语言中
|
|
编译不过
rust 数据默认move语义, 实现了drop trait,栈元素离开作用域时自动调用drop析构函数,用以释放指向的堆内存。
|
|
什么样的数据不是move语义呢:除非它实现了 Copy trait, 即copy语义,什么样的数据类型会被实现copy语义呢?
值类型 存在栈上 如int bool等,值类型执行赋值操作时会自动赋值一个副本。
引用类型 数据存在堆上,栈上只存放指向堆中数据的地址(指针),如可变长字符串,vector。
|
|
会按位复制(栈复制) 是copy语义 实现了Copy trait,一旦某类型实现copy trait,那么它在变量绑定、函数参数传递、函数返回值等场景下都是copy语义,而不是默认的move语义。
并不是所有的类型都可以手动实现Copy trait,如为结构体实现 Copy trait,需该结构体的所有类型都是实现Copy trait的。实现了drop析构函数的是不能实现Copy trait的。
Rust通过此标记对值语义和引用语义做了精准分类,来帮助编译器检测潜在的内存安全问题。所有权管理堆内存,即引用类型,此类型才需要保证内存安全。
对于copy类型 按位复制不会出现安全问题,对于禁止实现copy的类型,按位复制可能出问题,所以默认实现move语义,且通过RAII机制自动释放堆内存。 所有权规则:
- 堆内存上的数据类型都有一个称为所有者的变量(栈上)
- 任一时刻(生存周期)有且只有一个所有者
- 当所有者变量离开作用域,堆上的数据被自动释放
|
|
|
|
// 想复制堆上数据 可以用clone
所有权与函数
|
|
返回值与所有权
|
|
主动释放
|
|
|
|
思考:标准库中的std::mem::drop函数是怎样实现的呢?
|
|
Borrow(reference)
在每一个函数中都获取所有权并接着返回所有权太繁琐。
|
|
如果我们想要函数使用一个值但不获取所有权该怎么办呢? 定义一个新的calculate_length 函数,它以一个对象的引用作为参数而不是获取值的所有权:
|
|
取指针被称为借用, 语义上告诉编译器 没有对这块内存的所有权,只是借来用,离开作用域后不会drop堆上内存,且要归还。
可变与不可变借用
|
|
cannot borrow immutable borrowed content *some_string
as mutable 正如变量默认不可变,借用默认也不可修改
|
|
借用有以下几个规则:
- &mut型借用只能指向本身具有mut修饰的变量,对于只读变量,不可以有&mut型借用。
- &mut型借用指针存在的时候,被借用的变量本身会处于“冻结”状态。
|
|
- 借用指针不能比它指向的变量存在的时间更长。 避免悬垂引用
|
|
- 如果只有&型借用指针,那么能同时存在多个;如果存在&mut型借用指针,那么只能存在一个;一个可变引用不能与其他引用同时存在 避免数据竞争
|
|
- 一个引用的作用域从声明的地方开始一直持续到最后一次使用为止
|
|
可变引用保证了在写的时候没有任何指针可以读取该值的内存,不可变引用保证了内存不会在读取之后被写入新数据。
核心原则
共享不可变,可变不共享 Rust不是针对各式各样的场景,用case by case的方式来解决内存安全问题。而是通过一种统一的机制,高屋建瓴地解决这一类问题。 多个读同时存在是可以的,存在一个写的时候,其他的读写都不能同时存在。 &mut型借用也经常被称为“独占指针”, &型借用也经常被称为“共享指针”。
例1 共享不可变一定安全
|
|
例2 共享后不可变
|
|
例3 同时多个可变引用
|
|
例4 与cpp对比的一个?
|
|
|
|
error: cannot borrow arr
as mutable be cause it is also borrowed as immutable 在存在一个不可变引用时不能修改原变量的值
原文始发于iczc :Rust入门笔记