Rust 追踪工厂和产品之间所有权的惯用方法是什么?
我有一个类,它创建另一个类的实例。有时,它需要对其产品做出反应或以其他方式使用其产品。然而,如果它通过一个它不拥有的产品,它可能会制造麻烦。我有以下解决方案:Rust 追踪工厂和产品之间所有权的惯用方法是什么?,rust,factory,Rust,Factory,我有一个类,它创建另一个类的实例。有时,它需要对其产品做出反应或以其他方式使用其产品。然而,如果它通过一个它不拥有的产品,它可能会制造麻烦。我有以下解决方案: struct Parent { id: Option<*const Parent>, name: String, } impl Parent { fn new(name: String) -> Parent { Parent { id : None,
struct Parent {
id: Option<*const Parent>,
name: String,
}
impl Parent {
fn new(name: String) -> Parent {
Parent {
id : None,
name : name,
}
}
fn spawn(&mut self, name: String) -> Child {
if let None = self.id {
self.id = Some(self as *const Parent);
}
Child {
parent: self.id.unwrap(),
name,
}
}
fn is_parent(&self, child: &Child) -> bool {
if self.id.unwrap() == child.parent {
true
} else {
false
}
}
}
struct Child {
parent: *const Parent,
name: String,
}
fn main() {
let mut parent_one = Parent::new(String::from("Bob"));
let mut parent_two = Parent::new(String::from("Ben"));
let child_one = parent_one.spawn(String::from("Barry"));
let child_two = parent_two.spawn(String::from("Bishop"));
if parent_one.is_parent(&child_one) {
println!("{} is the parent of {}.",parent_one.name,child_one.name);
}
if parent_one.is_parent(&child_two) {
println!("{} is the parent of {}.",parent_one.name,child_two.name);
}
if parent_two.is_parent(&child_one) {
println!("{} is the parent of {}.",parent_two.name,child_one.name);
}
if parent_two.is_parent(&child_two) {
println!("{} is the parent of {}.",parent_two.name,child_two.name);
}
}
结构父级{
id:选项,
名称:String,
}
内隐父代{
fn新建(名称:字符串)->父级{
母公司{
id:没有,
姓名:姓名,,
}
}
fn spawn(&mut self,名称:String)->Child{
如果let None=self.id{
self.id=Some(self作为*常量父项);
}
孩子{
父项:self.id.unwrap(),
名称
}
}
fn是\u父对象(&self,子对象:&child)->bool{
如果self.id.unwrap()==child.parent{
真的
}否则{
假的
}
}
}
结构子对象{
父项:*常量父项,
名称:String,
}
fn main(){
让mut parent_one=parent::new(字符串::from(“Bob”);
让mut parent_two=parent::new(字符串::from(“Ben”);
让child_one=parent_one.spawn(字符串::from(“Barry”));
让child_two=parent_two.spawn(字符串::from(“Bishop”);
如果父对象是父对象(&子对象){
println!(“{}是{}的父对象。”,父对象名称,子对象名称);
}
如果父项为父项(子项为二){
println!(“{}是{}”的父项,父项为one.name,子项为two.name);
}
如果父项为父项(子项为一){
println!(“{}是{}的父项。”,父项2.name,子项1.name);
}
如果父项是父项(&child){
println!(“{}是{}的父对象。”,父对象名称,子对象名称);
}
}
我担心的第一件事是父母被销毁,地址被重新使用。也许需要一个时间戳来进一步确保所有权
更进一步,我想知道Rust是否有更好的方法来处理这种情况
编辑:
这只是一个很小的例子。完整的代码是一个链表库。目前我正在创建一个函数,允许从列表中删除节点。这是通过调用
List.remove(Node)
完成的。但是,我需要确保节点确实属于提供的列表。因为,如果删除头、尾或最后一个元素,则必须更新列表。如果提供列表和节点不匹配,则最终结果将不正确
编辑2:
我已经确认,重用内存地址肯定是一个问题。另外,虽然时间戳是有用的,但没有随机性,我担心它仍然不够。如果您愿意采用“全局递增ID”方法,那么您需要的是一个
ID
类型,具有这样的接口,您可以将其存储在父和子类型中:
pub struct Id(...);
impl Id {
pub fn new() -> Id { ... }
}
impl PartialEq<Id> for Id {
fn eq(&self, other: &Id) -> bool { ... }
}
impl Eq for Id { }
impl Clone for Id { fn clone(&self) -> Id { ... } }
此解决方案唯一的微妙之处是PhantomData
字段。它的存在是为了强制Id
不执行Send
或Sync
,因此对Id
的任何引用都仅限于创建它的线程。使用#![feature(negative_impls)]
您可以使用更清晰的impl解决方案!发送/!同步Id{}
,但这是不稳定的,因此我们只需添加一个不是Send
或Sync
的伪字段(因为原始指针不是Send/Sync,并且PhantomData
共享其参数的Send
/Sync
状态)
另见:
如果您确实需要从多个线程访问父线程
或子线程
,事情会变得稍微复杂一些。我们需要将下一个ID存储在全局原子整数变量中,但问题是没有“原子检查添加”这样的东西,因此我们无法像在单线程情况下那样简单地检测ID包装。代码将如下所示:
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Id(u64);
impl Id {
pub fn new() -> Id {
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
let id = todo!(); // what goes here?
Id(id)
}
}
pub fn new() -> Id {
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
let id = loop {
let id = NEXT_ID.load(Ordering::Relaxed);
if id == u64::MAX { panic!("Ran out of IDs!");
if let Ok(_) = NEXT_ID.compare_exchange(id, id + 1, Ordering::Relaxed, Ordering::Relaxed) {
break id
}
// another thread changed NEXT_ID after we checked for overflow, try again
};
Id(id)
}
您尚未指出您是否依赖于is_child
检查来确保内存安全或逻辑正确性。如果只是后者,您可以通过保留u64::MAX
来表示“我们没有Id”,并通过以下方式实现Id::new
:
pub fn new() -> Id {
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
if id == u64::MAX { panic!("Ran out of IDs!"); }
Id(id)
}
由于恐慌可以在Rust中恢复(也可以运行析构函数),并且上述仅在NEXT_PARENT
增加后检测溢出,因此在这种情况下,可能会在恐慌期间或之后创建两个相等的Id
s。如果这可能违反内存安全,您有两种选择:要么将panic更改为std::process::abort
,要么使用比较和交换循环检查溢出,避免实际增加计数器,如下所示:
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Id(u64);
impl Id {
pub fn new() -> Id {
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
let id = todo!(); // what goes here?
Id(id)
}
}
pub fn new() -> Id {
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
let id = loop {
let id = NEXT_ID.load(Ordering::Relaxed);
if id == u64::MAX { panic!("Ran out of IDs!");
if let Ok(_) = NEXT_ID.compare_exchange(id, id + 1, Ordering::Relaxed, Ordering::Relaxed) {
break id
}
// another thread changed NEXT_ID after we checked for overflow, try again
};
Id(id)
}
显然,此循环可能会带来较小的性能损失。最后,请记住,AtomicU64
是广泛可用的,但不是普遍可用的。如果可移植性是一个问题,请参阅并考虑使用<代码> AtomicUsize <代码>(虽然这可能显著增加在32位平台上溢出溢出的几率)。为什么不给每一个家长分配一个递增的整数ID,让孩子记住家长的ID呢?这只是一个简单的例子。完整的代码是一个链表库。我将提交一个编辑。是的,必须有更好的方法来做到这一点。在编译时进行检查是最好的。我甚至不能开始考虑如何在编译时进行检查。在C++中,我知道可以简单地将一个静态变量作为ID增加。但是,我避免使用外部库和不安全代码。code>Rc
和Arc
有它们自己的弱
引用,可以从子级使用它们来指向其父级。您可能希望将函数更改为只接受Rc
/Arc
wrappedself
。