初识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,
        }
    }
}

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

相关文章
|
2月前
|
Rust 安全 Java
30天拿下Rust之面向对象
30天拿下Rust之面向对象
41 0
|
Rust 编译器
Rust面向对象与Trait
Rust面向对象与Trait
|
3月前
|
Rust 安全 Go
揭秘Rust语言:为何它能让你在编程江湖中,既安全驰骋又高效超车,颠覆你的编程世界观!
【8月更文挑战第31天】Rust 是一门新兴的系统级编程语言,以其卓越的安全性、高性能和强大的并发能力著称。它通过独特的所有权和借用检查机制解决了内存安全问题,使开发者既能享受 C/C++ 的性能,又能避免常见的内存错误。Rust 支持零成本抽象,确保高级抽象不牺牲性能,同时提供模块化和并发编程支持,适用于系统应用、嵌入式设备及网络服务等多种场景。从简单的 “Hello World” 程序到复杂的系统开发,Rust 正逐渐成为现代软件开发的热门选择。
66 1
|
12天前
|
Rust 安全 Java
探索Rust语言的并发编程模型
探索Rust语言的并发编程模型
|
19天前
|
Rust 安全 区块链
探索Rust语言:系统编程的新选择
【10月更文挑战第27天】Rust语言以其安全性、性能和并发性在系统编程领域受到广泛关注。本文介绍了Rust的核心特性,如内存安全、高性能和强大的并发模型,以及开发技巧和实用工具,展示了Rust如何改变系统编程的面貌,并展望了其在WebAssembly、区块链和嵌入式系统等领域的未来应用。
|
20天前
|
Rust 安全 Java
编程语言新宠:Rust语言的特性、优势与实战入门
【10月更文挑战第27天】Rust语言以其独特的特性和优势在编程领域迅速崛起。本文介绍Rust的核心特性,如所有权系统和强大的并发处理能力,以及其性能和安全性优势。通过实战示例,如“Hello, World!”和线程编程,帮助读者快速入门Rust。
42 1
|
21天前
|
Rust 安全 编译器
编程语言新宠:Rust语言的特性、优势与实战入门
【10月更文挑战第26天】Rust语言诞生于2006年,由Mozilla公司的Graydon Hoare发起。作为一门系统编程语言,Rust专注于安全和高性能。通过所有权系统和生命周期管理,Rust在编译期就能消除内存泄漏等问题,适用于操作系统、嵌入式系统等高可靠性场景。
34 2
|
6天前
|
Rust 安全
深入理解Rust语言的所有权系统
深入理解Rust语言的所有权系统
16 0
|
12天前
|
Rust 安全 前端开发
探索Rust语言的异步编程模型
探索Rust语言的异步编程模型
|
22天前
|
Rust 安全 云计算
Rust语言入门:安全性与并发性的完美结合
【10月更文挑战第25天】Rust 是一种系统级编程语言,以其独特的安全性和并发性保障而著称。它提供了与 C 和 C++ 相当的性能,同时确保内存安全,避免了常见的安全问题。Rust 的所有权系统通过编译时检查保证内存安全,其零成本抽象设计使得抽象不会带来额外的性能开销。Rust 还提供了强大的并发编程工具,如线程、消息传递和原子操作,确保了数据竞争的编译时检测。这些特性使 Rust 成为编写高效、安全并发代码的理想选择。
17 0