Rust 结构体详解

2020-11-26 language rust

结构体是 Rust 中最常用的,同样使用起来也相对更加复杂。

简介

在 Rust 中结构体分成了三类:A) Regular 最常见的结构体,含字段及其类型;B) Tuple 字段名替换为了数字,允许析构方式获取;C) Unit 大小为零,仅作为标记,通常用于实现某个 trait 接口,但是无需成员变量。

与元组不同,结构体提供了字段名称,可以通过字段名直接进行访问,这与其它语言类似,只是为了方便使用提供了很多扩展。

struct Rectangle {
    width: u64,
    height: u64,
    name: String,
}

// 定义函数使用字段初始化简写创建
fn build_rectangle(width: u64, height: u64) -> Rectangle {
    Rectangle {
        width,
        height,
        name: String::from("Rectangle"),
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 20,
        height: 10,
        name: String::from("Rectangle"),
    };

    // 使用结构体更新语法从一个变量定义新变量
    let rect2 = Rectangle {
        name: String::from("NewRect"),
        ..rect1
    };
}

结构体更新语法允许通过 .. 定义剩余未显示设置的字段,不过仍然需要注意所有权的转移,上述的 name 变量是移动,而剩余则是复制,那么新老变量都可以继续使用。

关联函数

Rust 中的结构体定义和成员函数实现是分开的,这样成员函数可以根据功能点在不同的文件中实现,从业务逻辑上进行划分。有些函数与对象强相关,但是又不需要关联实例对象,例如构造器。

pub struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    pub fn new(width: u32, height: u32) -> Self {
        Rectangle { width, height }
    }
	fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle::new(30, 50);
    println!("Height={}, Width={}", rect.height, rect.width);
}

构造器是最常见的,Rust 并没有将 new 作为关键字,但是约定俗成将其作为构造器。因为没有 self 作为参数,也就不需要 rect.area() 这种通过对象的方式访问,函数又与结构体强相关,被称为关联函数,例如 String::from() 也是。

getter

结构体中成员变量默认是私有的,可以通过 pub 声明为公开,而且 Rust 允许成员变量和成员函数名称相同,这样就可以实现一个简单的 getter 访问器。

pub struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    pub fn new(width: u32, height: u32) -> Self {
        Rectangle { width, height }
    }
    pub fn width(&self) -> u32 {
        return self.width;
    }
	pub fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle::new(30, 50);
    println!("Area={}, Width={}", rect.area(), rect.width());
}

在库的相同模块中是可以直接访问私有变量的,如果是不同的模块之间就需要使用 getter 才可以了。

self

其中 &selfself: &Self 的简写,其中 Self 代指对应的结构体,而 self 就表示对应的实例了。另外,使用 self 时仍然存在所有权的概念:

  • &self 向函数传递的是一个引用,不会发生对象所有权的转移,通常也被称为借用。
  • &mut self 表示可变引用,在该函数结束之前其它代码是无法使用的,在该函数内可以修改。
  • self 向函数传递的是一个引用,会发生所有权转移,对象所有权会传递到函数中,使用较少。
struct Rectangle {
    width: u32,
    height: u32,
}

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

fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    println!("The area is {}", rect.area());
}

高级用法

生命周期

如果在结构体中引用某个外部变量,需要保证被引用的变量生命周期要长于结构体对象。

struct Option {
    width: u64,
    height: u64,
}

struct Rectangle<'a> {
    config: &'a Option,
    width: u64,
    height: u64,
    name: String,
}

impl<'a> Rectangle<'a> {
    pub fn new(opt: &'a Option) -> Rectangle {
        Rectangle {
            config: opt,
            width: opt.width,
            height: opt.height,
            name: String::from("Rectangle"),
        }
    }
    pub fn area(&self) -> u64 {
        self.height * self.width
    }
}

fn main() {
    let opt = Option{height: 10, width: 20};
    let rec = Rectangle::new(&opt);
    println!("{} area is {:?}, config width={}", rec.name, rec.area(), rec.config.width);
}

结合泛型

与常规的 Rust 泛型 类似,还可以通过 trait bounds 指定支持特定的 trait 才行。

struct Rectangle<T> {
    width: T,
    height: T,
}

impl<T> Rectangle<T>
where
    T: std::ops::Mul<Output = T> + Copy,
{
    fn area(&self) -> T {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };
    println!("The area is {}", rect.area());
}

注意,需要增加 where 指定约束,否则可能会导致如下报错。

consider restricting type parameter `T`: `: std::ops::Mul<Output = T>`