包含彼此了解的字段的结构

2023-11-29

我有一组需要相互了解才能合作的对象。这些对象存储在容器中。我试图对如何用 Rust 构建我的代码有一个非常简单的想法。

让我们打个比方。 AComputer包含:

  • 1 Mmu
  • 1 Ram
  • 1 Processor

In Rust:

struct Computer {
    mmu: Mmu,
    ram: Ram,
    cpu: Cpu,
}

为了使任何事情发挥作用,Cpu需要了解Mmu它链接到,并且Mmu需要知道Ram它链接到。

I do not想要Cpu to 按值聚合 the Mmu。它们的寿命不同:Mmu可以自己过自己的生活。只是碰巧我可以将它插入Cpu。然而,创建一个Cpu没有Mmu依附于它,因为它无法完成它的工作。之间也存在同样的关系Mmu and Ram.

所以:

  • A Ram可以自己生活。
  • An Mmu需要一个Ram.
  • A Cpu需要一个Mmu.

我怎样才能在 Rust 中模拟这种设计,一个一个结构体,其字段相互了解.

在 C++ 中,它的结构如下:

>

struct Ram
{
};

struct Mmu
{
  Ram& ram;
  Mmu(Ram& r) : ram(r) {}
};

struct Cpu
{
  Mmu& mmu;
  Cpu(Mmu& m) : mmu(m) {}
};

struct Computer
{
    Ram ram;
    Mmu mmu;
    Cpu cpu;
    Computer() : ram(), mmu(ram), cpu(mmu) {}
};

以下是我开始用 Rust 翻译它的方式:

struct Ram;

struct Mmu<'a> {
    ram: &'a Ram,
}

struct Cpu<'a> {
    mmu: &'a Mmu<'a>,
}

impl Ram {
    fn new() -> Ram {
        Ram
    }
}

impl<'a> Mmu<'a> {
    fn new(ram: &'a Ram) -> Mmu<'a> {
        Mmu {
            ram: ram
        }
    }
}

impl<'a> Cpu<'a> {
    fn new(mmu: &'a Mmu) -> Cpu<'a> {
        Cpu {
            mmu: mmu,
        }
    }
}

fn main() {
    let ram = Ram::new();
    let mmu = Mmu::new(&ram);
    let cpu = Cpu::new(&mmu);
}

这很好,但现在我只是找不到一种方法来创建Computer struct.

我开始于:

struct Computer<'a> {
    ram: Ram,
    mmu: Mmu<'a>,
    cpu: Cpu<'a>,
}

impl<'a> Computer<'a> {
    fn new() -> Computer<'a> {
        // Cannot do that, since struct fields are not accessible from the initializer
        Computer {
            ram: Ram::new(),
            mmu: Mmu::new(&ram),
            cpu: Cpu::new(&mmu),
        }

        // Of course cannot do that, since local variables won't live long enough
        let ram = Ram::new();
        let mmu = Mmu::new(&ram);
        let cpu = Cpu::new(&mmu);
        Computer {
            ram: ram,
            mmu: mmu,
            cpu: cpu,
        }
    }
}

好吧,无论如何,我将无法找到一种方法来引用它们之间的结构字段。我想我可以通过创建来想出一些东西Ram, Mmu and Cpu在堆上;并将其放入结构中:

struct Computer<'a> {
    ram: Box<Ram>,
    mmu: Box<Mmu<'a>>,
    cpu: Box<Cpu<'a>>,
}

impl<'a> Computer<'a> {
    fn new() -> Computer<'a> {
        let ram = Box::new(Ram::new());
        // V-- ERROR: reference must be valid for the lifetime 'a
        let mmu = Box::new(Mmu::new(&*ram));
        let cpu = Box::new(Cpu::new(&*mmu));
        Computer {
            ram: ram,
            mmu: mmu,
            cpu: cpu,
        }
    }
}

是的,没错,此时 Rust 无法知道我要转让所有权let ram = Box::new(Ram::new()) to the Computer,所以它将得到一生'a.

我一直在尝试各种或多或少的黑客方法来实现这一点,但我就是无法想出一个干净的解决方案。我最接近的是放弃参考并使用Option,但是我所有的方法都必须检查是否Option is Some or None,这是相当难看的。

我想我只是走错了路,试图在 Rust 中映射我在 C++ 中要做的事情,但这不起作用。这就是为什么我需要帮助找出创建此架构的惯用 Rust 方法。


在这个答案中,我将讨论解决这个问题的两种方法,一种是安全的 Rust,具有零动态分配和很少的运行时成本,但可能会受到限制,另一种是使用不安全不变量的动态分配。

安全的方法(Cell<Option<&'a T>)

use std::cell::Cell;

#[derive(Debug)]
struct Computer<'a> {
    ram: Ram,
    mmu: Mmu<'a>,
    cpu: Cpu<'a>,
}

#[derive(Debug)]
struct Ram;

#[derive(Debug)]
struct Cpu<'a> {
    mmu: Cell<Option<&'a Mmu<'a>>>,
}

#[derive(Debug)]
struct Mmu<'a> {
    ram: Cell<Option<&'a Ram>>,
}

impl<'a> Computer<'a> {
    fn new() -> Computer<'a> {
        Computer {
            ram: Ram,
            cpu: Cpu {
                mmu: Cell::new(None),
            },
            mmu: Mmu {
                ram: Cell::new(None),
            },
        }
    }

    fn freeze(&'a self) {
        self.mmu.ram.set(Some(&self.ram));
        self.cpu.mmu.set(Some(&self.mmu));
    }
}

fn main() {
    let computer = Computer::new();
    computer.freeze();

    println!("{:?}, {:?}, {:?}", computer.ram, computer.mmu, computer.cpu);
}

操场

与普遍看法相反,自我引用are事实上,在安全的 Rust 中是可能的,甚至更好,当你使用它们时,Rust 将继续为你强制执行内存安全。

使用以下方法获取自我、递归或循环引用所需的主要“技巧”&'a T是使用一个Cell<Option<&'a T>包含参考。如果没有Cell<Option<T>>包装纸。

该解决方案的巧妙之处是将结构的初始创建与正确的初始化分开。这有一个不幸的缺点,即通过在调用之前初始化并使用它,可能会错误地使用该结构freeze,但如果不进一步使用它就不会导致内存不安全unsafe.

结构体的最初创建只是为我们后来的黑客行为奠定了基础——它创建了Ram,它没有依赖关系,并设置Cpu and Mmu至无法使用的状态,包含Cell::new(None)而不是他们需要的参考资料。

然后,我们调用freeze方法,故意借用 self 的生命周期'a,或结构的完整生命周期。调用此方法后,编译器将阻止我们获取对Computer or移动Computer,因为任何一个都可能使我们持有的引用无效。这freeze方法然后设置Cpu and Mmu适当地通过设置Cells 包含Some(&self.cpu) or Some(&self.ram)分别。

After freeze称为我们的结构已准备好使用,但只能是不可变的。

不安全的方式(Box<T>从不移动T)

#![allow(dead_code)]

use std::mem;

// CRUCIAL INFO:
//
// In order for this scheme to be safe, Computer *must not*
// expose any functionality that allows setting the ram or
// mmu to a different Box with a different memory location.
//
// Care must also be taken to prevent aliasing of &mut references
// to mmu and ram. This is not a completely safe interface,
// and its use must be restricted.
struct Computer {
    ram: Box<Ram>,
    cpu: Cpu,
    mmu: Box<Mmu>,
}

struct Ram;

// Cpu and Mmu are unsafe to use directly, and *must only*
// be exposed when properly set up inside a Computer
struct Cpu {
    mmu: *mut Mmu,
}
struct Mmu {
    ram: *mut Ram,
}

impl Cpu {
    // Safe if we uphold the invariant that Cpu must be
    // constructed in a Computer.
    fn mmu(&self) -> &Mmu {
        unsafe { mem::transmute(self.mmu) }
    }
}

impl Mmu {
    // Safe if we uphold the invariant that Mmu must be
    // constructed in a Computer.
    fn ram(&self) -> &Ram {
        unsafe { mem::transmute(self.ram) }
    }
}

impl Computer {
    fn new() -> Computer {
        let ram = Box::new(Ram);

        let mmu = Box::new(Mmu {
            ram: unsafe { mem::transmute(&*ram) },
        });
        let cpu = Cpu {
            mmu: unsafe { mem::transmute(&*mmu) },
        };

        // Safe to move the components in here because all the
        // references are references to data behind a Box, so the
        // data will not move.
        Computer {
            ram: ram,
            mmu: mmu,
            cpu: cpu,
        }
    }
}

fn main() {}

操场

NOTE:由于接口不受限制,该解决方案并不完全安全Computer- 必须注意不允许别名或删除Mmu or Ram在计算机的公共界面中。

该解决方案使用了数据存储在 a 内的不变式Box永远不会移动 - 它的地址永远不会改变 - 只要Box仍然活着。 Rust 不允许您在安全代码中依赖于此,因为移动Box可能会导致其后面的内存被释放,从而留下悬空指针,但我们可以在不安全的代码中依赖它。

该解决方案的主要技巧是使用原始指针来访问Box<Mmu> and Box<Ram>将引用存储在Cpu and Mmu分别。这将为您提供一个基本安全的界面,并且不会阻止您移动Computer在有限的情况下,甚至可以改变它。

结束语

综上所述,我认为这两种方法都不应该是解决这个问题的真正方法。所有权是 Rust 的核心概念,它渗透到几乎所有代码的设计选择中。如果Mmu拥有RamCpu拥有Mmu,这就是您的代码中应该具有的关系。如果你使用Rc,您甚至可以保持共享底层片段的能力,尽管是一成不变的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

包含彼此了解的字段的结构 的相关文章

随机推荐

  • 通过嵌套字典键对 pandas 数据框进行分组

    我有一个 pandas 数据框 其中一列是字典类型 这是一个示例数据框 import pandas as pd df pd DataFrame a 1 2 3 b 4 5 6 version major 7 minor 1 major 8
  • 递归循环遍历 DOM 树并删除不需要的标签?

    tags array applet gt 1 script gt 1 html file get contents test html dom new DOMdocument dom gt loadHTML html xpath new D
  • 通过常见的键值对组合 JSON

    我目前正在解决一个问题 似乎无法解决这个问题 这是一些数据 以便您了解我在下面所说的内容 foo json Schedule deviceId 123 reservationId 123456 username jdoe deviceId
  • 由于写访问权限,无法使用 Android shell atrace 命令

    如果我尝试atrace工具来自adb壳里emulator atrace atrace error opening sys kernel debug tracing options overwrite No such file or dire
  • 如何创建全局辅助函数?

    我想创建一些全局辅助函数 我知道我必须将它们放在 App Code 中的 cshtml 文件中 我创建了这个文件 helper CreatePostForm string action string controller string id
  • 使用请求登录有问题的站点

    我正在尝试使用 requests 模块在 python 中创建一个脚本来登录到此site 我正在使用我的凭据 但我找不到任何方法来这样做 因为我看不到与请求一起发送所需的参数 在 Chrome 开发工具中 username SIMMTH i
  • 如何使用Maven PDF插件从Surefire Report生成PDF?

    运行 JUnit 测试后 我使用 Maven Surefire Report 插件 http maven apache org plugins maven surefire report plugin 生成 HTML 测试报告 这会产生以下
  • Kubernetes 中的 TCP 入口支持

    似乎 TCP 和 UDP 支持将在下一版本的 ingress nginx 控制器中被弃用 还有其他入口控制器支持 TCP 和 UDP 吗 或任何其他在 kubernetes 之外公开非 http 端口的解决方案 kubernetes 初学者
  • 如何在 Excel 工作表中查找精确值

    如何在 Excel 工作表中找到字符串值 我在尝试objRange Find 但这也给了我错误的地址 例如 我想要 Object paint 的地址 但它也给出 Object paint and stk 的地址 我应该如何获得精确的值 Se
  • Spring boot war文件部署在Tomcat上

    我使用 Spring Boot 1 2 4 RELEASEGS 休息服务源文件 我有 127 0 0 1 18 Jun 2015 09 59 25 0300 GET gs rest service 0 1 0 HTTP 1 1 404 10
  • Mac OS X:_tkinter.TclError:没有显示名称,也没有 $DISPLAY 环境变量

    正如我所说 我已经从 Macports 安装了 Python 3 3 现在 当我重点搜索 空闲 时 Idle Python 3 3 出现 但是 当我尝试单击它时 什么也没有发生 没有显示任何错误或任何东西 它显然不会启动 您认为可能有什么问
  • 为什么要在数据库中创建视图?

    何时以及为何有人决定需要在数据库中创建视图 为什么不直接运行一个普通的存储过程或选择呢 视图提供了多种好处 1 视图可以隐藏复杂性 如果您的查询需要连接多个表 或者具有复杂的逻辑或计算 则可以将所有逻辑编码到视图中 然后像选择表一样从视图中
  • 移动当前可执行文件c#

    我想将当前正在执行的程序集移动到 C 驱动器 当我尝试以下代码时 File Move Assembly GetEntryAssembly Location c 它给了我一个错误 mscorlib dll 中发生 System Unautho
  • Java线程安全数据库连接

    我正在编写一个 servlet 它通过访问和修改数据库中的某些表来处理每个请求 我希望与数据库的连接是线程安全的 我不想为此使用现有的库 框架 spring hibernate 等 我知道我可以通过以下方式使用 java 的 ThreadL
  • 与 multiprocessing.Pool 共享计数器

    我想用multiprocessing Value multiprocessing Lock在不同的进程之间共享一个计数器 例如 import itertools as it import multiprocessing def func x
  • .NET MAUI绑定ListView的ItemSelected事件到ViewModel

    我正在尝试将 ListView 的 ItemSelected 绑定到视图模型 但遇到了一些问题 由于我自己对其工作原理的误解 我有观点
  • Python 和 Ctypes:将结构体作为指针传递给函数以获取数据

    我已经查看了其他答案 但似乎无法使其发挥作用 我正在尝试调用 DLL 中的函数来与 SMBus 设备进行通信 该函数接受一个指向结构的指针 该结构有一个数组作为其字段之一 所以 In C typedef struct SMB REQUEST
  • 托管 Bean 内的回滚事务

    我想不在 EJB 内回滚事务 而是在 JSF 托管 bean 内回滚事务 在EJB内部我们可以使用SessionContext setRollBackOnly 但我可以在托管 bean 中使用什么 Stateless Local Accou
  • List和ArrayList这句话是什么意思?

    我正在阅读 Drools Planner 示例 并且经常遇到这样的代码 List
  • 包含彼此了解的字段的结构

    我有一组需要相互了解才能合作的对象 这些对象存储在容器中 我试图对如何用 Rust 构建我的代码有一个非常简单的想法 让我们打个比方 AComputer包含 1 Mmu 1 Ram 1 Processor In Rust struct Co