为什么通过提取方法进行重构会触发借用检查器错误?

2024-01-10

我的网络应用程序的架构可以简化为以下内容:

use std::collections::HashMap;

/// Represents remote user. Usually has fields,
/// but we omit them for the sake of example.
struct User;

impl User {
    /// Send data to remote user.
    fn send(&mut self, data: &str) {
        println!("Sending data to user: \"{}\"", data);
    }
}

/// A service that handles user data.
/// Usually has non-trivial internal state, but we omit it here.
struct UserHandler {
    users: HashMap<i32, User>,  // Maps user id to User objects.
    counter: i32  // Represents internal state
}

impl UserHandler {
    fn handle_data(&mut self, user_id: i32, data: &str) {
        if let Some(user) = self.users.get_mut(&user_id) {
            user.send("Message received!");
            self.counter += 1;
        }
    }
}

fn main() {
    // Initialize UserHandler:
    let mut users = HashMap::new();
    users.insert(1, User{});
    let mut handler = UserHandler{users, counter: 0};

    // Pretend we got message from network:
    let user_id = 1;
    let user_message = "Hello, world!";
    handler.handle_data(user_id, &user_message);
}

这工作正常。我想在中创建一个单独的方法UserHandler当我们已经确定具有给定 id 的用户存在时,它会处理用户输入。所以就变成了:

impl UserHandler {
    fn handle_data(&mut self, user_id: i32, data: &str) {
        if let Some(user) = self.users.get_mut(&user_id) {
            self.handle_user_data(user, data);
        }
    }

    fn handle_user_data(&mut self, user: &mut User, data: &str) {
        user.send("Message received!");
        self.counter += 1;
    }
}

突然编译不了!

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/main.rs:24:13
   |
23 |         if let Some(user) = self.users.get_mut(&user_id) {
   |                             ---------- first mutable borrow occurs here
24 |             self.handle_user_data(user, data);
   |             ^^^^                  ---- first borrow later used here
   |             |
   |             second mutable borrow occurs here

乍一看,错误非常明显:你不能有一个可变的引用self和一个属性self- 就像有两个可变引用self。但那又怎样呢,我do原始代码中有两个像这样的可变引用!

  1. 为什么这个简单的重构会触发借用检查器错误?
  2. 我如何解决它并分解UserHandler::handle_data像这样的方法?

如果您想知道为什么我想要这样的重构,请考虑这样一种情况:用户可以发送多种类型的消息,所有类型的消息都需要以不同的方式处理,但有一个共同的部分:必须知道哪些消息User对象发送此消息。


编译器阻止你借用是正确的HashMap两次。假设在handle_user_data()你也尝试过借self.users。你会违反 Rust 中的借用规则,因为你已经有了一个可变的借用,而且你只能拥有一个。

既然借不到self两次为你handle_user_data(),我会提出一个解决方案。我不知道它是否是最好的,但它的工作原理没有不安全且没有开销(我认为)。

这个想法是使用一个中间结构来借用其他字段self:

impl UserHandler {
    fn handle_data(&mut self, user_id: i32, data: &str) {
        if let Some(user) = self.users.get_mut(&user_id) {
            Middle::new(&mut self.counter).handle_user_data(user, data);
        }
    }
}

struct Middle<'a> {
    counter: &'a mut i32,
}

impl<'a> Middle<'a> {
    fn new(counter: &'a mut i32) -> Self {
        Self {
            counter
        }
    }

    fn handle_user_data(&mut self, user: &mut User, data: &str) {
        user.send("Message received!");
        *self.counter += 1;
    }
}

这样,编译器就知道我们不能借用users twice.

如果您只有一两个东西可以借用,一个快速的解决方案是使用一个将它们作为参数的关联函数:

impl UserHandler {
    fn handle_user_data(user: &mut User, data: &str, counter: &mut i32) {
        // ...
    }
}

我们可以改进这个设计:

struct UserHandler {
    users: HashMap<i32, User>, // Maps user id to User objects.
    middle: Middle,              // Represents internal state
}

impl UserHandler {
    fn handle_data(&mut self, user_id: i32, data: &str) {
        if let Some(user) = self.users.get_mut(&user_id) {
            self.middle.handle_user_data(user, data);
        }
    }
}

struct Middle {
    counter: i32,
}

impl Middle {
    fn new(counter: i32) -> Self {
        Self {
            counter
        }
    }

    fn handle_user_data(&mut self, user: &mut User, data: &str) {
        user.send("Message received!");
        self.counter += 1;
    }
}

现在我们确信我们没有开销并且语法更加清晰。

更多信息请参阅 Niko Matsakis 的博客文章NLL 之后:过程间冲突 http://smallcultfollowing.com/babysteps/blog/2018/11/01/after-nll-interprocedural-conflicts/。将此答案映射到博客文章:

  • 解决方案#1 ->“将结构视为一般但极端的解决方案”部分
  • 解决方案#2 ->“自由变量作为一般但极端的解决方案”部分(此处表示为关联函数)
  • 解决方案#3 ->“分解为可能的修复”部分
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么通过提取方法进行重构会触发借用检查器错误? 的相关文章

随机推荐

  • jQuery 仅序列化 div 中的元素

    我想得到同样的效果jQuery serialize 但我只想返回给定的子元素div 结果示例 single Single2 multiple Multiple radio radio1 没问题 只需使用以下内容即可 这将与序列化表单完全相同
  • js中能获取之前的历史状态对象吗?

    当我点击后退或前进按钮并且 popstate 事件触发时 我可以获得前一个状态的状态对象吗 因为不是 e state 提供的状态对象 而是我刚刚后退 转发的状态对象 或者 我可以检测按下的是后退按钮还是前进按钮 我需要这个 因为我有多个子系
  • 匹配点集的算法

    我有两组点A and B 而点可以是 2D 或 3D 两套尺寸相同n 相当低 5 20 我想知道这些集合的一致性如何 也就是说 理想情况下 我会找到点之间的配对 使得所有欧几里得对距离的总和d A B 是最小的 所以 d A B sum i
  • PhoneGap FileReader/readAsDataURL 不触发回调

    我正在使用 PhoneGap Build 构建 iOS v7 1 应用程序并使用weinre http people apache org pmuellr weinre docs latest 进行调试 我正在使用媒体捕获插件和文件 API
  • 获取中间件中的路由定义

    有谁知道是否可以获取用于触发路线的路径 例如 假设我有这个 app get user id function req res 使用以下简单的中间件 function req res next req 我希望能够得到 user id在中间件中
  • Instagram Streaming API 的 Logstash 输入插件

    我想阅读 Instagram 上的活动 我想知道我是否可以使用 Logstash 来做到这一点 类似于使用 Twitter 输入插件从 Twitter 读取事件 但 Instagram 没有输入插件 还有其他方法可以使用 Logstash
  • Pygame 中自上而下的运动

    如果这个问题之前已经被问过 我很抱歉 但我到处都检查过 但找不到答案 如何在 pygame 中进行自上而下的移动 如果我只使用矩形 这会很容易 但我将使用单个字符精灵 例如 如果我按d为了让玩家向右走 它会向我显示他向右走的角色精灵并将角色
  • Android KitKat 中的 WebView 渲染问题

    我一直在开发一个具有 WebView 的应用程序 其中从资产加载静态页面 也使用 JavaScript 此 WebView 在 KitKat 中不起作用 它保持空白 我知道在 kitkat 的 WebView 中发生的渲染引擎 webkit
  • Azure 存储模拟器无法初始化,并显示“数据库‘AzureStorageEmulatorDb57’不存在”

    我在使用 Azure 存储模拟器时遇到问题 我尝试重新初始化数据库并收到以下错误 这是在安装 Visual Studio 2019 预览版之后发生的 但这可能只是巧合 我尝试了一个小时左右让它运行 然后放弃了 只是使用 保留我的文件 选项重
  • 减少功能如何工作?

    据我了解 reduce 函数需要一个列表l和一个函数f 然后 它调用该函数f列表的前两个元素 然后重复调用该函数f与下一个列表元素和上一个结果 因此 我定义了以下函数 以下函数计算阶乘 def fact n if n 0 or n 1 re
  • 该模型没有从输入中返回损失 - LabSE 错误

    我想使用小队数据集微调 LabSE 以进行问答 我收到这个错误 ValueError The model did not return a loss from the inputs only the following keys last
  • Cassandra Nodejs DataStax 驱动程序不会通过准备好的语句执行返回新添加的列

    在架构中添加一对列后 我想通过以下方式选择它们select 反而select 返回旧的列集并且没有新的列 根据文档推荐 我使用 prepare true 来平滑 JavaScript 浮点数和 Cassandra ints bigints
  • 如何使用温莎城堡实例化基于 web.config 文件的类?

    我正在开发一个 ASP NET MVC 应用程序 我想根据 web config 中编写的设置实例化一个类
  • 如何在 Django 中使用单个查询集选择记录并更新它?

    我如何运行update and select相同的声明queryset而不必执行两个查询 一项选择对象 和一个更新对象 SQL 中的等效内容类似于 update my table set field 1 some value where p
  • 如何使用正则表达式格式化数字[重复]

    这个问题在这里已经有答案了 我遇到了涉及格式化数字的问题 在巴西 我们有一种称为 CPF 的文件 这是每个公民都拥有的一种个人身份证 以下是格式正确的 CPF 号码示例 096 156 487 09 我正在尝试使用正则表达式来格式化包含 C
  • ‘php.exe’不被识别为内部或外部命令、可运行程序或批处理文件

    php exe 不被识别为内部或外部命令 可运行的程序或批处理文件 即使我已将 PHP 添加到环境变量中 为什么仍会出现该错误 我的环境变量PATH如下所示 C Program Files NVIDIA Corporation PhysX
  • 如何在8086汇编中减去两个64位整数

    编写一个名为 SUB64 的程序 用 0x0160 和 0x0164 中的 64 位整数减去内存位置 0x0150 和 0x0154 中的 64 位整数 将结果存储在内存位置 0x0170 和 0x0174 中 我理解将其分成更小的部分背后
  • python re.findall() 与交替的子字符串

    如果我在正则表达式交替中有另一个字符串或模式的子字符串 或 子模式 如下所示 r abcd bc 预期的行为是什么re compile r abcd bc findall abcd bcd bc ab 尝试一下 我得到了 正如预期的那样 a
  • 使用 Square Connect API 访问客户信息

    是否可以使用 Square Connect API 访问有关商家客户的任何信息 最理想的信息是客户输入的收据电子邮件地址 但某种类型的唯一客户 ID 可以很好地确定回头客 纵观整个Square Connect API 文档 https co
  • 为什么通过提取方法进行重构会触发借用检查器错误?

    我的网络应用程序的架构可以简化为以下内容 use std collections HashMap Represents remote user Usually has fields but we omit them for the sake