Rust编程入门:从零基础到掌握核心概念的完整指南

  • King
  • 发布于 19小时前
  • 阅读 106

引言最近身边不少朋友对Rust编程语言产生了浓厚兴趣,纷纷向我咨询如何快速入门。作为一门系统级编程语言,Rust以其内存安全、并发安全和零成本抽象等特性,在开发者社区中越来越受欢迎。传统的学习方式往往需要花费大量时间阅读文档和书籍,但对于现代开发者来说,借助AI工具学习新技术已经成为一种高效

引言

最近身边不少朋友对Rust编程语言产生了浓厚兴趣,纷纷向我咨询如何快速入门。作为一门系统级编程语言,Rust以其内存安全、并发安全和零成本抽象等特性,在开发者社区中越来越受欢迎。

传统的学习方式往往需要花费大量时间阅读文档和书籍,但对于现代开发者来说,借助AI工具学习新技术已经成为一种高效的学习方式。AI不仅可以帮助我们快速理解复杂的概念,还能根据个人学习进度提供个性化的指导。

今天,我们就一起借助AI的力量来学习Rust的基础知识。无论你是完全没有编程经验的新手,还是有一定其他语言基础的开发者,这篇教程都将带你从零开始,逐步掌握Rust的核心概念。

让我们开始这段有趣的Rust学习之旅吧!

目录

  1. 介绍
  2. 环境搭建
  3. 基础语法
  4. 变量绑定
  5. 数据类型
  6. 函数
  7. 控制流
  8. 所有权
  9. 引用与借用
  10. 结构体
  11. 枚举
  12. 泛型
  13. Trait
  14. 错误处理
  15. 模块系统
  16. Cargo包管理器

介绍

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);
}

变量遮蔽(Shadowing)

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有两种基本的浮点类型:f32f64(默认):

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);
}

复合类型

元组(Tuple)

元组具有固定长度,一旦声明就不能增长或缩小:

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);
}

数组(Array)

数组具有固定长度,所有元素必须是相同类型:

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;
}

控制流

if 表达式

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);
}

循环

loop循环

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("result 的值是: {}", result);
}

while循环

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);
        number -= 1;
    }

    println!("起飞!!!");
}

for循环

遍历集合:

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无需垃圾回收器就可以保证内存安全。

所有权规则

  1. Rust中的每个值都有一个被称为其所有者的变量
  2. 值在任一时刻只能有一个所有者
  3. 当所有者离开作用域时,这个值将被丢弃

作用域

fn main() {
    {
        let s = "hello"; // s 从这里开始生效
        println!("{}", s);
    } // s 在这里不再有效
}

String类型

字符串字面量在编译时就知道其内容,所以它们被存储在二进制文件中。String类型为了支持可变性、增长性的文本内容,需要在堆上分配内存:

fn main() {
    let s = String::from("hello");
    println!("{}", s);
}

移动(Move)

当把一个值赋给另一个变量时,会发生移动:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 被移动到 s2

    // 下面这行会报错,因为 s1 不再有效
    // println!("{}, world!", s1);

    println!("{}, world!", s2);
}

克隆(Clone)

如果我们确实需要深度复制堆上的数据,可以使用clone方法:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("s1 = {}, s2 = {}", s1, s2);
}

拷贝(Copy)

对于像整数这样的基本类型,它们完全存储在栈上,所以拷贝非常快:

fn main() {
    let x = 5;
    let y = x;

    println!("x = {}, y = {}", x, y);
}

具有Copy特征的类型包括:

  • 所有整数类型
  • 布尔类型
  • 所有浮点类型
  • 字符类型
  • 元组(当且仅当其包含的类型也都具有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枚举

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

Trait告诉Rust编译器某个特定类型拥有可能与其他类型共享的功能。

定义Trait

pub trait Summary {
    fn summarize(&self) -> String;
}

为类型实现Trait

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());
}

Trait Bound

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!

当执行panic!宏时,程序会打印一个错误信息并展开调用栈:

fn main() {
    panic!("crash and burn");
}

可恢复错误与Result

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和expect

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提供了一个强大的模块系统来组织代码。

module

使用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();
}

public和private

默认情况下,所有内容都是私有的:

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关键字

使用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包管理器

Cargo是Rust的构建系统和包管理器。

创建项目

cargo new project_name

项目结构

project_name/
├── Cargo.toml
└── src/
    └── main.rs

Cargo.toml文件

[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学习之旅提供坚实的基础。

学习任何编程语言都需要大量的实践,建议你在阅读完本教程后:

  1. 动手编写更多的Rust代码来巩固所学知识
  2. 尝试构建一些小项目来应用这些概念
  3. 深入阅读官方文档以了解更多高级特性
  4. 参与Rust社区交流,与其他开发者分享经验

记住,编程是一项实践性很强的技能,只有通过不断地编码和调试,才能真正掌握一门语言的精髓。祝你在Rust的学习道路上越走越远,享受系统编程带来的乐趣!

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
King
King
0x56af...a0dd
擅长Rust/Solidity/FunC/Move开发