17、可变引用(练习)

可变引用

在上一节的习题中,我们完成了这样的代码

impl Ticket {
    pub fn title(&self) -> &String {
        &self.title
    }

    pub fn description(&self) -> &String {
        &self.description
    }

    pub fn status(&self) -> &String {
        &self.status
    }
}

这段代码的作用是提供了Ticket结构体的封装,有三个方法可以返回内部的私有成员。

但是在调用时,会出现所有权丢失的问题,所以我们引入了借用机制,向方法传递实例的借用,并且也返回借用。

 

现在,我们有办法更改 Ticket 实例的字段,而无需在此过程中占用所有权。接下来,让我们看看如何通过setter方法来增强我们的 Ticket 结构。

Setters

Setter方法允许用户更改Ticket私有字段的值,同时确保一些该字段的规则限制(比如不能将Ticket的title设置为空)。

 

有两个常见的方式实现setters:

  • 使用self作为输入
  • 使用&mut self作为输入

使用self作为输入

我们大概要这样编码:

impl Ticket {
    pub fn set_title(mut self, new_title: String) -> Self {
        // Validate the new title [...]
        self.title = new_title;
        self
    }
}

这段代码占用了self的所有权,改变了title,最后返回一个修改过后的Ticket实例

如果我们想要使用的话,就必须在左侧用原变量接收返回值,不然会丢失所有权:

let ticket = Ticket::new(
    "Title".into(), 
    "Description".into(), 
    "To-Do".into()
);
let ticket = ticket.set_title("New title".into());

上面的代码使用了variable shadowing(变量遮蔽)机制。

 

有一个很酷的地方,当我们需要一次修改多个字段时,我们可以使用多个调用链接在一起方式

let ticket = ticket
    .set_title("New title".into())
    .set_description("New description".into())
    .set_status("In Progress".into());

使用&mut self作为输入

impl Ticket {
    pub fn set_title(&mut self, new_title: String) {
        // Validate the new title [...]
        
        self.title = new_title;
    }
}

和上面最大的不同是,我们不需要返回这个实例了。

这次的方式使用self的可变引用作为输入,改变了title。不需要返回任何东西了。

 

这种使用方法也是很舒服的:

let mut ticket = Ticket::new(
    "Title".into(),
    "Description".into(),
    "To-Do".into()
);
ticket.set_title("New title".into());

// Use the modified ticket

所有权保留在调用者手中,因此原始的Ticket实例依然有效。我们不需要重新分配结果,不过我们需要将ticket标记为可变引用,这里的重点在可变上,上一节的重点是引用

 

&mut-setter相比前面的第一种方案有一个缺点:我们不能将多个调用链接在一起,因为它们不返回修改后的Ticket实例,我们必须单独调用每个setter

练习

image-20250907083343638

 

这道题目让我们实现setter方法,同时封装三个字段校验器,并修改new方法里面的逻辑,使用字段校验器

// TODO: Add &mut-setters to the `Ticket` struct for each of its fields.
//   Make sure to enforce the same validation rules you have in `Ticket::new`!
//   Even better, extract that logic and reuse it in both places. You can use
//   private functions or private static methods for that.

pub struct Ticket {
    title: String,
    description: String,
    status: String,
}

impl Ticket {
    pub fn new(title: String, description: String, status: String) -> Ticket {
       // TODO:
        validate_title(&title);
        if description.is_empty() {
            panic!("Description cannot be empty");
        }
        validate_description(&description);
        validate_status(&status);

        Ticket {
            title,
            description,
            status,
        }
    }

    pub fn title(&self) -> &String {
        &self.title
    }

    pub fn description(&self) -> &String {
        &self.description
    }

    pub fn status(&self) -> &String {
        &self.status
    }

   pub fn set_title(&mut self, title: String) {
       validate_title(&title);
       self.title = title;
   }

   pub fn set_description(&mut self, description: String) {
       validate_description(&description);
       self.description = description;
   }

   pub fn set_status(&mut self, status: String) {
       validate_status(&status);
       self.status = status;
   }
}

fn validate_title(title: &String) {
    if title.is_empty() {
        panic!("Title cannot be empty");
    }
    if title.len() > 50 {
        panic!("Title cannot be longer than 50 bytes");
    }
}

fn validate_description(description: &String) {
    if description.is_empty() {
        panic!("Description cannot be empty");
    }
    if description.len() > 500 {
        panic!("Description cannot be longer than 500 bytes");
    }
}

fn validate_status(status: &String) {
    if status != "To-Do" && status != "In Progress" && status != "Done" {
        panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed");
    }
}

字段校验器的参数要传递String的引用,这是因为如果不传递引用,调用者就没有这个String的所有权啦,所以必须传递引用。

另外我想吐槽一下,起码告诉我函数签名是什么吧,你不说我怎么敢确定参数就是&mut self和一个String类型的变量呢?

难道这也是考察的一部分?虽然前面的理论部分给的set例子就是String变量,其实看报错信息,也能看出来(

或许这是RustRover的良苦用心吧(doge

 

这一节的学习就到这里了,拜拜

阅读剩余
THE END