引言最近身边不少朋友对Rust编程语言产生了浓厚兴趣,纷纷向我咨询如何快速入门。作为一门系统级编程语言,Rust以其内存安全、并发安全和零成本抽象等特性,在开发者社区中越来越受欢迎。传统的学习方式往往需要花费大量时间阅读文档和书籍,但对于现代开发者来说,借助AI工具学习新技术已经成为一种高效
最近身边不少朋友对Rust编程语言产生了浓厚兴趣,纷纷向我咨询如何快速入门。作为一门系统级编程语言,Rust以其内存安全、并发安全和零成本抽象等特性,在开发者社区中越来越受欢迎。
传统的学习方式往往需要花费大量时间阅读文档和书籍,但对于现代开发者来说,借助AI工具学习新技术已经成为一种高效的学习方式。AI不仅可以帮助我们快速理解复杂的概念,还能根据个人学习进度提供个性化的指导。
今天,我们就一起借助AI的力量来学习Rust的基础知识。无论你是完全没有编程经验的新手,还是有一定其他语言基础的开发者,这篇教程都将带你从零开始,逐步掌握Rust的核心概念。
让我们开始这段有趣的Rust学习之旅吧!
Rust是一门系统级编程语言,专注于安全、并发和性能。它提供了零成本抽象、内存安全和线程安全等特性。
主要特点:
使用rustup安装Rust(推荐方式):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
安装完成后,重启终端或运行以下命令加载环境变量:
source $HOME/.cargo/env
验证安装:
rustc --version
cargo new hello_world
cd hello_world
运行程序:
cargo run
让我们先看一个简单的Rust程序:
fn main() {
println!("Hello, world!");
}
fn 关键字用于定义函数main 是程序入口点println! 是一个宏,用于打印文本到控制台在Rust中,我们使用 let 关键字来声明变量:
fn main() {
let x = 5;
println!("x 的值是: {}", x);
}
默认情况下,变量是不可变的。如果要使变量可变,需要使用 mut 关键字:
fn main() {
let mut x = 5;
println!("x 的值是: {}", x);
x = 6;
println!("x 的新值是: {}", x);
}
Rust允许使用相同的名称声明新的变量,这称为变量遮蔽:
fn main() {
let x = 5;
let x = x + 1;
let x = x * 2;
println!("x 的值是: {}", x); // 输出: x 的值是: 12
}
Rust是静态类型语言,每个值都有明确的数据类型。
| 长度 | 有符号 | 无符号 |
|---|---|---|
| 8-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| 128-bit | i128 | u128 |
| arch | isize | usize |
fn main() {
let a: i32 = 98_222;
let b: u32 = 100_123;
println!("a: {}, b: {}", a, b);
}
Rust有两种基本的浮点类型:f32 和 f64(默认):
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
println!("x: {}, y: {}", x, y);
}
fn main() {
let t = true;
let f: bool = false;
println!("t: {}, f: {}", t, f);
}
fn main() {
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
println!("{} {} {}", c, z, heart_eyed_cat);
}
元组具有固定长度,一旦声明就不能增长或缩小:
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tup;
println!("元组的值是: {}, {}, {}", x, y, z);
// 也可以通过索引访问
let five_hundred = tup.0;
let six_point_four = tup.1;
let one = tup.2;
println!("通过索引访问: {}, {}, {}", five_hundred, six_point_four, one);
}
数组具有固定长度,所有元素必须是相同类型:
fn main() {
let a = [1, 2, 3, 4, 5];
let b: [i32; 5] = [1, 2, 3, 4, 5];
let c = [3; 5]; // 等价于 [3, 3, 3, 3, 3]
println!("数组a的第一个元素: {}", a[0]);
println!("数组b的第二个元素: {}", b[1]);
println!("数组c的所有元素: {:?}", c);
}
函数使用 fn 关键字声明。Rust代码使用 snake case 作为函数和变量名的规范风格:
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("另一个函数");
}
fn main() {
another_function(5);
}
fn another_function(x: i32) {
println!("x 的值是: {}", x);
}
函数可以有多个参数:
fn main() {
print_labeled_measurement(5, 'h');
}
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("测量值是: {}{}", value, unit_label);
}
在箭头(->)后声明函数的返回类型。在Rust中,函数的返回值等同于函数体最后一个表达式的值:
fn main() {
let x = five();
println!("x 的值是: {}", x);
}
fn five() -> i32 {
5
}
使用return关键字提前返回:
fn main() {
let x = plus_one(5);
println!("x 的值是: {}", x);
}
fn plus_one(x: i32) -> i32 {
return x + 1;
}
fn main() {
let number = 3;
if number < 5 {
println!("条件为真");
} else {
println!("条件为假");
}
}
使用if作为表达式:
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("number 的值是: {}", number);
}
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("result 的值是: {}", result);
}
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("起飞!!!");
}
遍历集合:
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("值是: {}", element);
}
}
使用Range:
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("起飞!!!");
}
所有权是Rust最独特的特性之一,它让Rust无需垃圾回收器就可以保证内存安全。
fn main() {
{
let s = "hello"; // s 从这里开始生效
println!("{}", s);
} // s 在这里不再有效
}
字符串字面量在编译时就知道其内容,所以它们被存储在二进制文件中。String类型为了支持可变性、增长性的文本内容,需要在堆上分配内存:
fn main() {
let s = String::from("hello");
println!("{}", s);
}
当把一个值赋给另一个变量时,会发生移动:
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 被移动到 s2
// 下面这行会报错,因为 s1 不再有效
// println!("{}, world!", s1);
println!("{}, world!", s2);
}
如果我们确实需要深度复制堆上的数据,可以使用clone方法:
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
}
对于像整数这样的基本类型,它们完全存储在栈上,所以拷贝非常快:
fn main() {
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
}
具有Copy特征的类型包括:
为了避免每次传递值都进行移动,我们可以使用引用。
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("'{}' 的长度是 {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
Rust编译器确保引用永远不会变成悬垂状态:
// 这个函数无法通过编译
fn dangle() -> &String {
let s = String::from("hello");
&s // 返回对s的引用,但s即将离开作用域
} // s 在这里被销毁,引用指向了无效的内存
正确的做法是直接返回String:
fn no_dangle() -> String {
let s = String::from("hello");
s // 所有权被移动出去
}
结构体是用于创建自定义数据类型的蓝图。
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
println!("用户名: {}", user1.username);
println!("邮箱: {}", user1.email);
}
fn main() {
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
println!("新邮箱: {}", user1.email);
}
当变量名与字段名相同时:
fn main() {
let email = String::from("someone@example.com");
let username = String::from("someusername123");
let user1 = User {
email, // 等价于 email: email
username, // 等价于 username: username
active: true,
sign_in_count: 1,
};
println!("用户名: {}", user1.username);
}
创建一个与现有实例几乎相同的新实例:
fn main() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1 // 使用user1的其余字段
};
println!("用户1登录次数: {}", user1.sign_in_count);
println!("用户2登录次数: {}", user2.sign_in_count);
}
没有命名字段的结构体:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
println!("黑色: ({}, {}, {})", black.0, black.1, black.2);
println!("原点: ({}, {}, {})", origin.0, origin.1, origin.2);
}
没有任何字段的结构体:
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
使用impl块为结构体定义方法:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// 关联函数(不是方法)
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
let rect3 = Rectangle { width: 60, height: 45 };
println!(
"矩形面积是 {} 平方像素",
rect1.area()
);
println!("rect1 能容纳 rect2 吗? {}", rect1.can_hold(&rect2));
println!("rect1 能容纳 rect3 吗? {}", rect1.can_hold(&rect3));
let sq = Rectangle::square(3);
println!("正方形: {:#?}", sq);
}
枚举允许你通过列举可能的值来定义一个类型。
enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
route(four);
route(six);
}
fn route(ip_kind: IpAddrKind) {
// 处理IP地址类型
}
enum IpAddr {
V4(String),
V6(String),
}
fn main() {
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
println!("家庭地址: {:?}", home);
println!("回环地址: {:?}", loopback);
}
更复杂的例子:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let m1 = Message::Quit;
let m2 = Message::Move { x: 12, y: 24 };
let m3 = Message::Write(String::from("hello"));
let m4 = Message::ChangeColor(255, 255, 255);
}
Option枚举用于处理可能存在或不存在的值:
fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
println!("{:?}", some_number);
println!("{:?}", some_string);
println!("{:?}", absent_number);
}
使用match处理Option:
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("{:?}", six);
println!("{:?}", none);
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
泛型允许我们编写适用于多种类型的代码。
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("最大数字是 {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("最大字符是 {}", result);
}
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
println!("整数点: ({}, {})", integer.x, integer.y);
println!("浮点数点: ({}, {})", float.x, float.y);
}
不同类型的泛型结构体:
struct Point<T, U> {
x: T,
y: U,
}
fn main() {
let both_integer = Point { x: 5, y: 10 };
let both_float = Point { x: 1.0, y: 4.0 };
let integer_and_float = Point { x: 5, y: 4.0 };
println!("整数点: ({}, {})", both_integer.x, both_integer.y);
println!("浮点数点: ({}, {})", both_float.x, both_float.y);
println!("混合点: ({}, {})", integer_and_float.x, integer_and_float.y);
}
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
let p2 = Point { x: 3.0, y: 4.0 };
println!("距离原点: {}", p2.distance_from_origin());
}
Trait告诉Rust编译器某个特定类型拥有可能与其他类型共享的功能。
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
pub trait Summary {
fn summarize(&self) -> String {
String::from("(阅读更多...)")
}
}
带默认实现的完整示例:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(来自 {} 的文章...)", self.summarize_author())
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from("当然,正如我所说,我就是个专家..."),
reply: false,
retweet: false,
};
println!("1. {}", tweet.summarize());
}
pub fn notify(item: &impl Summary) {
println!("新闻摘要!{}", item.summarize());
}
等价的Trait Bound语法:
pub fn notify<T: Summary>(item: &T) {
println!("新闻摘要!{}", item.summarize());
}
多个Trait Bound:
pub fn notify(item: &(impl Summary + Display)) {
// 实现
}
或者:
pub fn notify<T: Summary + Display>(item: &T) {
// 实现
}
通过where子句简化复杂Trait Bound:
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
// 实现
}
Rust将错误分为两大类:可恢复错误和不可恢复错误。
当执行panic!宏时,程序会打印一个错误信息并展开调用栈:
fn main() {
panic!("crash and burn");
}
Result枚举有两个成员:Ok和Err:
enum Result<T, E> {
Ok(T),
Err(E),
}
处理Result:
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("打开文件时出现问题: {:?}", error)
},
};
}
匹配不同的错误:
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(error) => panic!("创建文件时出现问题: {:?}", error),
},
other_error => panic!("打开文件时出现问题: {:?}", other_error),
},
};
}
unwrap是match的一个快捷方法:
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
}
expect允许指定panic时的错误信息:
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("无法打开hello.txt");
}
使用?操作符传播错误:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
链式调用:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
Rust提供了一个强大的模块系统来组织代码。
使用mod关键字声明模块:
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
fn main() {
// 使用绝对路径
crate::front_of_house::hosting::add_to_waitlist();
// 使用相对路径
front_of_house::hosting::add_to_waitlist();
}
默认情况下,所有内容都是私有的:
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("桃子"),
}
}
}
}
pub fn eat_at_restaurant() {
let mut meal = back_of_house::Breakfast::summer("黑麦");
meal.toast = String::from("全麦");
println!("我想要{}吐司", meal.toast);
}
使用use关键字将路径引入作用域:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
使用pub use重新导出:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
Cargo是Rust的构建系统和包管理器。
cargo new project_name
project_name/
├── Cargo.toml
└── src/
└── main.rs
[package]
name = "project_name"
version = "0.1.0"
edition = "2024"
[dependencies]
# 构建项目
cargo build
# 运行项目
cargo run
# 检查代码但不生成可执行文件
cargo check
# 构建发布版本
cargo build --release
# 运行测试
cargo test
# 生成文档
cargo doc
# 发布到crates.io
cargo publish
在Cargo.toml中添加依赖:
[dependencies]
rand = "0.8.5"
然后运行:
cargo build
Cargo会自动下载并编译依赖。
通过这篇教程,我们一起借助AI的力量探索了Rust编程语言的核心概念。从基础语法到高级特性,我们系统地学习了变量、函数、所有权、结构体、枚举、泛型、Trait、错误处理以及模块系统等重要知识点。
Rust虽然有着相对较陡峭的学习曲线,但其强大的内存安全保障和卓越的性能表现使其成为系统编程领域的优秀选择。希望这篇教程能为你开启Rust学习之旅提供坚实的基础。
学习任何编程语言都需要大量的实践,建议你在阅读完本教程后:
记住,编程是一项实践性很强的技能,只有通过不断地编码和调试,才能真正掌握一门语言的精髓。祝你在Rust的学习道路上越走越远,享受系统编程带来的乐趣!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!