初识Rust中的面向对象

简介: 初识Rust中的面向对象
Rust是一门支持多范式的编程语言,在Rust中,我们可以通过Struct很好的进行面向对象编程。本篇文章将介绍Rust的Struct。

Rust与Struct

什么是Struct?这是Rust面向对象的解决方案。

struct——结构体,通常有以下用途:

  • 自定义的数据类型。
  • 为相关联的值命名,打包=>有意义的组合。

1.定义并实例化Struct

定义struct

  • 使用struct关键字,并且为整个struct命名
  • 在花括号内,为所有字段(Field)定义名称和类型

例如:

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

每个key:value,隔开,最后一项也要加,

实例化struct

想要使用struct,需要创建struct的实例:

  • 为每个字段指定具体值
  • 无需按声明的顺序进行指定

例子:

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        email: String::from("kevinqian@qian.com"),
        username: String::from("KevinQian"),
        active: true,
        sign_in_count: 886,
    };
}

取得struct的某个值

使用点标记法.

如果要改变struct的某个值,需要在实例化时使用mut关键字

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let mut user1 = User {
        email: String::from("kevinqian@qian.com"),
        username: String::from("KevinQian"),
        active: true,
        sign_in_count: 886,
    };
    user1.email = String::from("888@qq.com");
    println!("{}", user1.active); //true
}

注意:一旦struct的实例是可变的,那么实例中所有的字段都是可变的。

struct作为函数的返回值

fn build_user(email: String, username: String) -> User {
    User {
        username: username,
        email: email,
        sign_in_count: 1,
        active: true,
    }
}

字段初始化简写

与js的解构语法相似,当字段名与字段值对应变量名相同时,就可以使用字段初始化简写的方式:

fn build_user(email: String, username: String) -> User {
    User {
        username,
        email,
        sign_in_count: 1,
        active: true,
    }
}

struct更新语法

当你想基于某个struct实例来创建一个新实例时(某些值与原值相同),可以使用struct更新语法:

如果我们不使用这个语法,我们写出来的代码是这样的:

let user2 = User {
    email: String::from("kevinqian@qian.com"),
    username: String::from("KevinQian"),
    active: user1.active,
    sign_in_count: user1.sign_in_count,
};

类似于js中的剩余操作符,在Rust中我们可以使用struct的更新语法来进行简写:

let user2 = User {
    email: String::from("kevinqian@qian.com"),
    username: String::from("KevinQian"),
    ..user1//注意不需要逗号,
};

Tuple struct

可定义类似tuple的struct,叫做tuple struct

  • Tuple struct整体有个名,但里面的元素没有名
  • 适用:想给整个tuple起名,并让它不同于其它tuple,而且又不需要给每个元素起名

定义tuple struct:使用struct关键字,后边是名字,以及里面元素的类型

例子:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
println!("{},{},{}", black.0, black.1, black.2); //0,0,0

注意:black和origin是不同的类型,即使它们的值相同。因为它们是不同tuple struct的实例。

Unit-Like Struct(没有任何字段)

可以定义没有任何字段的struct,叫做Unit-Like Struct(因为与(),单元类型类型)

适用于需要在某个类型上实现某个trait(接口),但是在里面又没有想要存储的数据

Struct数据的所有权

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

上面的字段使用了String而不是&str:

  • 该Struct实例拥有其所有的数据
  • 只要struct实例是有效的,那么里面的字段数据也是有效的

struct里也可以存放引用,但这需要使用生命周期

  • 生命周期保证只要struct实例是有效的,那么里面的引用也是有效的。
  • 如果struct里面存储引用,而不使用生命周期,就会报错:
struct User {
  username: &String, //missing lifetime specifier
  email: &String,
  sign_in_count: u64,
  active: bool,
}

Struct例子

计算长方形面积

如果我们直接使用函数:

fn main() {
    let w = 30;
    let l = 50;

    println!("{}", area(w, l)); //1500
}

fn area(width: u32, length: u32) -> u32 {
    width * length
}

我们使用元组作为函数参数:

fn main() {
    let rect = (30, 50);

    println!("{}", area(rect)); //1500
}

fn area(dim: (u32, u32)) -> u32 {
    dim.0 * dim.1
}

这样看来我们将长和宽放在一起了,但代码的可读性更差了,我们分不清楚哪个是长哪个是宽

我们可以使用struct来写这段代码:

struct Rectangle {
    width: u32,
    length: u32,
}

fn main() {
    let rect = Rectangle {
        width: 30,
        length: 50,
    };

    println!("{}", area(&rect)); //1500
}

fn area(rect: &Rectangle) -> u32 {
    rect.width * rect.length
}

在上面的例子中,我们的函数中使用的&Rectangle,借用了Rectangle的一个实例rect。所以在我们传入了rect后依然可以使用rect:

println!("{}", rect); //`Rectangle` doesn't implement `std::fmt::Display`

我们在12行后输入了以下代码,但是报错了:Rectangle doesn't implement std::fmt::Display

这是因为我们使用prinln!()这个宏,通过{}来进行打印的时候,默认使用的是std::fmt::Display这个trait(接口)

我们按照编译器的提示:use {:?} (or {:#?} for pretty-print) instead

println!("{:?}", rect); //`Rectangle` doesn't implement `Debug`

编译器提示:note: add #[derive(Debug)] to Rectangle or manually impl Debug for Rectangle

我们将这段加入我们struct的上面,执行成功:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    length: u32,
}
//...
println!("{:?}", rect);//Rectangle { width: 30, length: 50 }

rust编译器提供了打印调试信息的功能(debug),但我们需要在struct进行手动打开。就是在struct上加入#[derive(Debug)]

但是这样打印出的数据太紧凑了,我们可以在:之间加入#

#[derive(Debug)]
struct Rectangle {
    width: u32,
    length: u32,
}
//...
println!("{:#?}", rect);
// Rectangle {
//     width: 30,
//     length: 50,
// }

实际上,Rust提供了很多trait让我们可以进行derive(派生),这可以对我们自定义的类型添加许多功能。

上面的#[derive(Debug)]就让我们的struct使用了std::fmt::Debug方法。

struct的方法

方法与函数类似:fn关键字、名称、参数、返回值

方法与函数的不同之处:

  • 方法是在struct(或enum、trait对象)的上下文中的定义
  • 第一个参数是self,表示方法被调用的struct实例

定义方法

在impl块中定义方法

方法的第一个参数可以是&self,也可以获得其所有权或可变借用。和其他参数一样。

#[derive(Debug)]
struct Rectangle {
    width: u32,
    length: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.length
    }
}

fn main() {
    let rect = Rectangle {
        width: 30,
        length: 50,
    };

    println!("{}", rect.area()); //1500
    println!("{:#?}", rect);
    // Rectangle {
    //     width: 30,
    //     length: 50,
    // }
}

通过impl块,我们可以获取更好的代码组织。

方法调用的运算符

Rust会自动引用或者解引用

  • 在调用方法时就会发生这种行为

在调用方法时,Rust根据情况自动添加&、&mut或者*(解引用),以便object可以匹配方法的签名。

下面两行代码效果相同:

p1.distance(&p2);//distance(&self,&p)
(&p1).distance(&p2);

方法参数

#[derive(Debug)]
struct Rectangle {
    width: u32,
    length: u32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.length > other.length
    }
}

fn main() {
    let rect1 = Rectangle {
        length: 30,
        width: 50,
    };
    let rect2 = Rectangle {
        length: 10,
        width: 40,
    };
    let rect3 = Rectangle {
        length: 35,
        width: 55,
    };

    println!("{}", rect1.can_hold(&rect2)); //true
    println!("{}", rect1.can_hold(&rect3)); //false
}

关联函数

可以在impl块里定义不把self作为第一个参数的函数,它们叫关联函数(不是方法)。关联函数可以理解为静态方法(比如js中,静态方法只能用类调用)。

  • 例如:String::from()

关联函数通常用于构造器:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    length: u32,
}

impl Rectangle {
//...
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            length: size,
        }
    }
}

fn main() {
    let s = Rectangle::square(20);
}

调用的时候使用::符号而不是.

::既可以用于关联函数,还可以运用于模块创建的命名空间

多个impl块

每个struct允许拥有多个impl块:

struct Rectangle {
    width: u32,
    length: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.length
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.length > other.length
    }
}

impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            length: size,
        }
    }
}

我们可以像上面这样写,但是在这个例子中我们这样做没啥必要。

相关文章
|
Rust 编译器
Rust面向对象与Trait
Rust面向对象与Trait
|
21天前
|
Rust 安全 Go
揭秘Rust语言:为何它能让你在编程江湖中,既安全驰骋又高效超车,颠覆你的编程世界观!
【8月更文挑战第31天】Rust 是一门新兴的系统级编程语言,以其卓越的安全性、高性能和强大的并发能力著称。它通过独特的所有权和借用检查机制解决了内存安全问题,使开发者既能享受 C/C++ 的性能,又能避免常见的内存错误。Rust 支持零成本抽象,确保高级抽象不牺牲性能,同时提供模块化和并发编程支持,适用于系统应用、嵌入式设备及网络服务等多种场景。从简单的 “Hello World” 程序到复杂的系统开发,Rust 正逐渐成为现代软件开发的热门选择。
37 1
|
1月前
|
Rust 安全 编译器
初探 Rust 语言与环境搭建
Rust 是一门始于2006年的系统编程语言,由Mozilla研究员Graydon Hoare发起,旨在确保内存安全而不牺牲性能。通过所有权、借用和生命周期机制,Rust避免了空指针和数据竞争等问题,简化了并发编程。相较于C/C++,Rust在编译时预防内存错误,提供类似C++的语法和更高的安全性。Rust适用于系统编程、WebAssembly、嵌入式系统和工具开发等领域。其生态系统包括Cargo包管理器和活跃社区。学习资源如"The Book"和"Rust by Example"帮助新手入门。安装Rust可通过Rustup进行,支持跨平台操作。
102 2
初探 Rust 语言与环境搭建
|
21天前
|
Rust 安全 程序员
Rust 语言的防错机制太惊人了!安全编码从此不再是难题,快来一探究竟!
【8月更文挑战第31天】《安全编码原则:Rust 语言中的防错机制》探讨了代码安全的重要性,并详细介绍了Rust语言如何通过内存安全模型、所有权与借用规则等特性,在编译阶段检测并阻止潜在错误,如缓冲区溢出和悬空指针。文章还讨论了类型安全、边界检查等其他安全特性,并提出了遵循不可变引用、避免裸指针及充分测试等实用编码原则,以进一步提升代码质量和安全性。随着Rust在软件开发中的应用日益广泛,掌握其安全编码原则变得尤为重要。
31 0
|
21天前
|
Rust 安全 调度
从零构建梦想操作系统:用Rust语言亲手打造专属内核,你也可以成为系统开发者!
【8月更文挑战第31天】开发操作系统内核虽具挑战,却也充满乐趣。本文将指导你从零开始,使用Rust语言构建一个简单的操作系统内核。首先安装Rust环境和交叉编译工具链,然后创建新项目`my_kernel`。通过修改`Cargo.toml`和编写启动函数,结合串口输出和`multiboot2`库,最终使用QEMU运行内核。此教程旨在帮助你理解Rust在系统开发中的应用,激发深入探索的兴趣。
37 1
|
21天前
|
Rust 安全 算法
揭秘Rust语言如何重塑区块链安全:打造坚不可摧的分布式账本新篇章!
【8月更文挑战第31天】自比特币诞生以来,区块链技术凭借其去中心化和不可篡改的特点备受关注。为了应对安全性挑战,Rust 语言凭借其内存安全特性逐渐成为区块链开发的优选。本文探讨了 Rust 如何助力区块链实现更安全的分布式账本。通过示例展示了 Rust 在避免内存泄漏、空指针引用及数据竞争等方面的优势,预示着 Rust 在高性能、高安全性需求的区块链应用中拥有广阔前景。
32 1
|
19天前
|
Rust Linux Go
Rust/Go语言学习
Rust/Go语言学习
|
20天前
|
开发者 vr&ar 机器学习/深度学习
Xamarin 开发者的未来趋势展望:掌握跨平台开发新机遇,引领移动应用创新潮流与技术变革方向
【8月更文挑战第31天】Xamarin 作为领先的跨平台开发框架,通过 C# 和 .NET 框架实现一次编写、多平台运行,简化了 iOS、Android 和 Windows 应用的开发流程。未来几年,Xamarin 开发者将面临跨平台开发普及、云集成、机器学习、AR/VR、性能优化及安全性等关键趋势。通过学习新技术并积极采用新工具,开发者能够提升应用质量和用户体验,如利用 Azure AD B2C 实现身份认证,从而在竞争激烈的市场中脱颖而出。
40 0
|
20天前
|
Rust 安全 开发者
惊爆!Xamarin 携手机器学习,开启智能应用新纪元,个性化体验与跨平台优势完美融合大揭秘!
【8月更文挑战第31天】随着互联网的发展,Web应用对性能和安全性要求不断提高。Rust凭借卓越的性能、内存安全及丰富生态,成为构建高性能Web服务器的理想选择。本文通过一个简单示例,展示如何使用Rust和Actix-web框架搭建基本Web服务器,从创建项目到运行服务器全程指导,帮助读者领略Rust在Web后端开发中的强大能力。通过实践,读者可以体验到Rust在性能和安全性方面的优势,以及其在Web开发领域的巨大潜力。
29 0
|
20天前
|
开发者 API 开发框架
Xamarin 在教育应用开发中的应用:从课程笔记到互动测验,全面解析使用Xamarin.Forms构建多功能教育平台的技术细节与实战示例
【8月更文挑战第31天】Xamarin 作为一款强大的跨平台移动开发框架,在教育应用开发中展现了巨大潜力。它允许开发者使用单一的 C# 代码库构建 iOS、Android 和 Windows 应用,确保不同设备上的一致体验。Xamarin 提供广泛的 API 支持,便于访问摄像头、GPS 等原生功能。本文通过一个简单的教育应用示例——课程笔记和测验功能,展示了 Xamarin 在实际开发中的应用过程。从定义用户界面到实现保存笔记和检查答案的逻辑,Xamarin 展现了其在教育应用开发中的高效性和灵活性。
27 0