Rust这几年越来越火,连续多年被评为最受程序员喜爱的编程语言。很多人想学习Rust,但被它独特的所有权系统和借用检查器吓到了,觉得太难学。
其实Rust没有想象中那么难,只要方法对,入门还是很快的。这篇文章就从零开始,一步步带你入门Rust,适合有其他编程语言基础的初学者。
为什么学Rust
在开始之前,先说说为什么要学Rust。
第一个原因是性能。Rust的性能和C/C++相当,没有垃圾回收,没有运行时开销,适合写高性能的系统级代码。操作系统、游戏引擎、数据库、浏览器内核,这些对性能要求极高的领域,Rust都能胜任。
第二个原因是安全。Rust的所有权系统和借用检查器能在编译时就发现很多内存安全问题,比如空指针、悬垂指针、数据竞争等。用Rust写的代码,运行时出内存错误的概率大大降低。
第三个原因是生态。Rust的生态越来越成熟,包管理工具cargo很好用,第三方库也越来越丰富。WebAssembly、命令行工具、网络服务、嵌入式开发,Rust都有很好的支持。
第四个原因是就业。越来越多的公司开始用Rust,Linux内核、Android、Windows都在引入Rust。掌握Rust技能,在就业市场上会很有竞争力。
环境搭建
第一步是搭建Rust开发环境。
Rust的安装很简单,官方推荐用rustup来管理Rust版本。在Mac和Linux上,打开终端执行一条命令就行:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
在Windows上,去官网下载rustup-init.exe,双击运行就行。
安装完成后,打开新的终端,执行rustc --version和cargo --version,如果能看到版本号,说明安装成功了。
编辑器推荐用VS Code,装上rust-analyzer插件,这是目前最好的Rust语言服务器,提供代码补全、类型提示、跳转定义等功能。再装上CodeLLDB插件,可以调试Rust代码。
第一个程序
环境搭好了,来写第一个Rust程序。
用cargo new hello_rust创建一个新项目,cargo会自动生成项目结构。进入项目目录,执行cargo run,就能看到输出"Hello, world!"。
Rust的项目结构很简单,src/main.rs是入口文件,Cargo.toml是项目配置文件。Cargo.toml里可以定义项目信息、依赖、构建配置等。
来看看main.rs的内容:
fn main() { println!("Hello, world!"); }
fn是函数关键字,main是入口函数,println!是宏(注意后面有感叹号),用来打印输出。Rust的语句以分号结尾,表达式不需要分号。
基础语法
接下来看看Rust的基础语法。
变量用let声明,默认是不可变的,要修改需要加mut关键字:
let x = 5; // 不可变 let mut y = 10; // 可变 y = 20; // 可以修改
这是Rust的一个重要设计:变量默认不可变,鼓励你写不可变的代码,减少bug。
数据类型方面,Rust是静态类型语言,编译时就要确定类型。但很多时候编译器可以自动推断类型,不需要显式声明。基本类型包括整数(i32、u64等)、浮点数(f32、f64)、布尔值(bool)、字符(char)等。
函数用fn声明,参数要显式指定类型,返回值用->指定:
fn add(a: i32, b: i32) -> i32 { a + b // 注意没有分号,这是返回值 }
Rust的函数最后一个表达式就是返回值,不需要return关键字。当然也可以用return提前返回。
控制流和其他语言差不多,if、else、loop、while、for都有。Rust的if是表达式,可以返回值:
let number = if condition { 5 } else { 10 };
所有权系统
所有权是Rust最核心的概念,也是最难理解的部分。
Rust的所有权规则很简单:每个值都有一个所有者,同一时间只能有一个所有者,所有者离开作用域时,值会被自动释放。
举个例子:
fn main() { let s1 = String::from("hello"); let s2 = s1; // s1的所有权移动给了s2 println!("{}", s1); // 报错!s1已经没有所有权了 }
在Rust里,把s1赋值给s2,不是复制,而是移动(move)。移动之后s1就不能用了,因为所有权已经交给s2了。这样就避免了两个变量指向同一块内存,释放时出现double free的问题。
如果想复制,可以用clone方法:
let s2 = s1.clone(); // 深拷贝,s1还能用
基本类型(整数、浮点数、布尔值等)实现了Copy trait,赋值的时候会自动复制,不会移动:
let x = 5; let y = x; // x复制给y,x还能用 println!("{}", x); // 正常
引用和借用
所有权移动虽然安全,但有时候不方便。比如写一个函数计算字符串长度,你不想把字符串的所有权传进去,因为传进去之后调用者就不能用了。
这时候可以用引用(reference)。引用就像指针,指向某个值,但不拥有它的所有权。创建引用的操作叫借用(borrowing)。
fn calculate_length(s: &String) -> usize { s.len() } // s离开作用域,但因为它不拥有所有权,所以不会释放值
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // 传引用,s1的所有权还在 println!("{}的长度是{}", s1, len); // 正常 }
引用分为不可变引用(&T)和可变引用(&mut T)。不可变引用只能读,可变引用可以读写。
借用有两个重要规则:第一,同一时间可以有多个不可变引用,但只能有一个可变引用;第二,可变引用和不可变引用不能同时存在。这两个规则保证了编译时就能避免数据竞争。
结构体和枚举
结构体用struct声明:
struct User { name: String, age: u32, active: bool, }
创建实例:
let user = User { name: String::from("张三"), age: 30, active: true, };
访问字段用点号:user.name。
枚举用enum声明:
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), }
Rust的枚举很强大,每个变体可以带不同类型的数据。配合match表达式,可以优雅地处理各种情况。
模式匹配
Rust的模式匹配是一个很强大的特性,用match关键字:
match value { pattern1 => { / 代码 / } pattern2 => { / 代码 / } _ => { / 默认情况 / } }
match必须穷举所有可能,编译器会检查有没有遗漏。比如匹配Option<T>的时候,必须处理Some和None两种情况,不能只处理Some。
if let是match的语法糖,当你只关心一种情况的时候,可以用if let代替match:
if let Some(value) = option { println!("{}", value); }
错误处理
Rust的错误处理和其他语言不太一样,没有异常,而是用Result<T, E>类型表示可能失败的操作:
enum Result<T, E> { Ok(T), Err(E), }
成功的时候返回Ok(value),失败的时候返回Err(error)。调用者必须处理错误,可以用match,也可以用?运算符传播错误:
fn readfile() -> Result<String, io::Error> { let mut s = String::new(); File::open("hello.txt")?.readto_string(&mut s)?; Ok(s) }
?运算符放在返回Result的函数调用后面,如果是Err就直接返回错误,如果是Ok就取出里面的值。这样错误处理的代码就很简洁。
集合类型
Rust标准库提供了几个常用的集合类型。
Vec<T>是动态数组,用Vec::new()创建,用push添加元素,用索引访问:
let mut v: Vec<i32> = Vec::new(); v.push(1); v.push(2); println!("{}", v[0]); // 1
String是字符串类型,用String::from()创建,用push_str追加:
let mut s = String::from("hello"); s.push_str(", world");
HashMap<K, V>是哈希表,用HashMap::new()创建,用insert插入,用get查询:
use std::collections::HashMap; let mut map = HashMap::new(); map.insert(String::from("key"), 10); println!("{:?}", map.get("key")); // Some(10)
泛型和trait
泛型用<T>表示,可以用在函数、结构体、枚举上:
fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest }
trait类似于其他语言的接口,定义一组行为:
trait Summary { fn summarize(&self) -> String; }
为类型实现trait:
impl Summary for User { fn summarize(&self) -> String { format!("{},{}岁", self.name, self.age) } }
trait可以作为参数类型,也可以作为返回类型,是Rust实现多态的方式。
学习建议
最后给一些学习建议。
第一,不要急。Rust的所有权和借用确实需要时间来理解,不要因为一开始编译不过就放弃。多写多练,慢慢就有感觉了。
第二,善用编译器。Rust的编译器提示是所有语言里最友好的,它不仅告诉你哪里错了,还会告诉你怎么改。仔细读错误信息,很多问题编译器已经给出了解决方案。
第三,读官方文档。Rust的官方文档写得非常好,《The Rust Programming Language》这本书是最好的入门教材,免费在线阅读。还有Rust by Example,通过例子学习Rust。
第四,多写项目。光看教程没用,要动手写。可以从简单的命令行工具开始,比如grep的简化版、待办事项工具等。在实践中学习,进步最快。
第五,加入社区。Rust的社区很友好,遇到问题可以去论坛、Discord、微信群提问。很多大佬都很乐意帮助新手。
写在最后
Rust是一门很有特点的语言,它的所有权系统、借用检查器、模式匹配等特性,会让你对编程有新的理解。
学习Rust可能比学其他语言慢一些,但一旦入门了,你会发现它的好处。代码更安全、性能更高、重构更放心,这些都是Rust带来的价值。
希望这篇入门指南能帮你迈出学习Rust的第一步。编程的路很长,我们一起加油。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录