锈迹-如何创建一个全局的、可变的单例?

2020-07-22 22:44:41

在系统中只有一个实例化的情况下创建和使用结构的最佳方式是什么?是的,这是必要的,它是OpenGL子系统,制作多个副本并到处传递它会增加混乱,而不是减轻它。

单身人士需要尽可能高效。似乎不可能在静态区域中存储任意对象,因为它包含带有析构函数的VEC。第二种选择是在静态区域上存储一个(不安全的)指针,该指针指向分配了单例的堆。在保持语法简洁的同时,执行此操作的最方便、最安全的方式是什么?

您看过OpenGL现有的Rust绑定如何处理同样的问题吗?--谢泼德。

是的,这是必要的,它是OpenGL子系统,制作多个副本并到处传递它会增加混乱,而不是减轻它。=>;这不是必需品的定义,它可能(一开始)很方便,但不是必需的。--K.Matthieu M.。

是的,你说得有道理。尽管OpenGL无论如何都是一个很大的状态机,我几乎可以肯定在任何地方都不会有它的克隆,使用它只会导致OpenGL错误。--斯捷文库切拉(Stevenkucera)。

总体上避免全局状态。相反,应在早期(可能在Main中)的某个位置构造对象,然后将对该对象的可变引用传递到需要它的位置。这通常会使您的代码更容易推理,并且不需要那么多的向后弯曲。

在决定需要全局可变变量之前,请仔细看看镜子中的自己。在极少数情况下,它是有用的,所以这就是为什么它值得知道怎么做的原因。

惰性静态板条箱可以省去一些手工创建单例的苦差事。下面是一个全局可变向量:

Use lazy_static::lazy_static;//1.4.0use std::sync::mutex;lazy_static!{static ref array:mutex<;vec<;u8>;>;=mutex::new(vec![]);}fn do_a_call(){ARRAY.lock().unwire().ush(1);}fn main(){do_a_call();调用{}";,ARRAY.lock().unwire().len());}。

您还可以使用一个卢旺达Lock而不是一个Mutex来允许多个并发读取器。

Once_cell板条箱可以省去一些手工创建单例的苦差事。下面是一个全局可变向量:

使用ONCE_CELL::SYNC::LAZY;//1.3.1使用STD::SYNC::Mutex;静态数组:LAZY<;Mutex<;Vec<;u8>;>;>;=Lazy::New(||Mutex::New(vec![]));fn do_a_call(){ARRAY.lock().unwire().ush(1);}fn main。Println!(";调用{}";,ARRAY.lock().unwire().len());}。

您还可以使用一个卢旺达Lock而不是一个Mutex来允许多个并发读取器。

如果只需要跟踪整数值,可以直接使用原子:

使用std::sync::atom::{AtomicUsize,Ording};Static Call_Count:AtomicUsize=AtomicUsize::New(0);fn do_a_call(){call_COUNT.fetch_add(1,order::SeqCst);}fn main(){do_a_call();println!(";call{}";,call_COUNT.load(order:SeqCst);}fn main(){do_a_call();println!(";调用{}";,call_COUNT.load(排序:SeqCst)。

这在很大程度上是从Rust 1.0的stdin实现中抄袭过来的,并对现代的Rust进行了一些调整。您还应该看看io::Lazy的现代实现。我已经对每一行的功能进行了内联注释。

使用std::sync::{Arc,Mutex,Once};使用std::Time::Duration;使用std::{mem,thread};#[Derate(Clone)]struct SingletonReader{//由于我们将在多个线程中使用,因此需要保护//并发访问内部:Arc<;Mutex<;U8>;>;,}FN Singleton()->;SingletonReader{//。Unsafe{ONCE.call_once(||{//make it let singleton=SingletonReader{Internal:arc::new(Mutex::New(0)),};//将其放入堆中,这样它就可以超过这次调用Singleton=mem::transmute(Box::new(Singleton);}));//现在我们分发一份可以安全并发使用的数据副本。(*singleton).clone()}}fn main(){//让我们在几个线程中使用Singleton让线程:VEC<;_>;=(0..10).map(|i|{线程::Span(Move||{线程::Slear(Duration::From_Millis(I*10);let s=singleton();let mut data=s.inner.lock().unwire();*data。//让';为线程中的线程定期检查_in 0u8..20{线程::SLEEP(持续时间::From_Millis(5));let s=singleton();let data=s.inner.lock().unwire();println!(";it:{}";,*data);}}。

它是:0是:1它是:1它是:2它是:3它是:3它是:4它是:5它是:5它是:6它是:7它是:8它是:9它是:9它是:9。

此代码使用Rust 1.42.0编译。标准输入的真正实现使用了一些不稳定的特性来尝试释放分配的内存,而这段代码没有这样做。

实际上,您可能希望让SingletonReader实现Deref和DerefMut,这样您就不必自己深入对象并锁定它。

请注意,您仍然可以使用普通Rust作用域和模块级隐私来控制对STATIC或LAZY_STATIC变量的访问。这意味着您可以在模块中或甚至在函数内部声明它,并且不能在该模块/函数外部访问它。这有利于控制访问:

Use lazy_static::lazy_static;//1.2.0fn only_here(){lazy_static!{static ref name:string=string::from(";hello,world!";);}println!(";{}";,&;*name);}fn not_here(){println!(";{}";,&;*name);}。

错误[E0425]:在此作用域中找不到值`NAME`-->;src/lib.rs:12:22|12|println!(";{}";,&;*name);|^在此作用域中找不到。

但是,该变量仍然是全局的,因为它只有一个实例存在于整个程序中。

经过深思熟虑后,我确信不使用Singleton,而是完全不使用全局变量,并将所有内容传递出去。使代码更具自文档化功能,因为可以清楚地知道哪些函数可以访问呈现器。如果我想改回单身,比起换回单身,这样做要容易得多。--斯捷文库切拉(Stevenkucera)。

谢谢你的回答,它帮了我很大的忙。我只是想,我应该在这里发表评论来描述我所看到的LAZY_STATIC!的有效用例。我使用它来连接一个允许加载/卸载模块(共享对象)的C应用程序,Ruust代码就是这些模块之一。我看不到比使用全局加载更多的选择,因为我根本无法控制main()以及核心应用程序如何与我的模块交互。我基本上需要一个可以在我的mod加载后在运行时添加的东西的向量。-米歇尔·莫伊塞斯·席尔瓦。

@MoisesSilva总是会有一些需要单身的理由,但在许多使用它的情况下,使用它是没有必要的。在不知道您的代码的情况下,C应用程序可能会允许每个模块返回一个用户数据,然后将其传递回每个模块的方法中。这是C代码的典型扩展模式。如果应用程序不允许这一点,并且您不能更改它,那么是的,单例可能是一个很好的解决方案。-约翰·谢普马斯特。

@worik你能解释一下为什么吗?我不鼓励人们做一些在大多数语言中都是糟糕想法的事情(就连OP也同意,对于他们的应用程序来说,全局是一个糟糕的选择)。这通常就是这个意思。然后,我展示了两种解决方案来说明如何做到这一点。我刚刚在Rust1.24.1中测试了lazy_static示例,它完全正常工作。这里任何地方都没有外部静电。也许你需要检查一下你那端的东西,以确保你已经完全理解了答案。-约翰·谢普马斯特。

@worik如果您需要有关如何使用板条箱的基础知识的帮助,我建议您重新阅读Rust编程语言。关于创建猜谜游戏的章节展示了如何添加依赖项。-约翰·谢普马斯特。

/EMPTY_ATTACK_TABLE定义一个空的攻击表,用于初始化攻击表pub const Empty_Attack_TABLE:AttackTable=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,];LAZY_STATIC!{/Knight_Attack是骑士酒吧静态参考Knight_Attack的攻击表:AttackTable={let mut at=Empty_Attack_table;for sq in 0.board_area{at[sq]=JUMP_ATK(sq,&;Knight_deltas,0);}at};...。

使用ONCE_CELL::SYNC::LAZY;.../AttackTable类型记录国际象棋棋盘pub类型AttackTable=[Bitboard;Board_Area]的每个方格的攻击位板;/EMPTY_ATTACK_TABLE定义一个空的攻击表,用于初始化攻击表pub const Empty_Attack_TABLE:AttackTable=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,];/Knight_Attack是骑士酒吧静态Knight_Attack:lazy<;AttackTable>;=Lazy::new的攻击表(||{let mut at=Empty_Attack_table;for sq in 0..board_area{at[sq]=Jump_Attas(sq,&;Knight_deltas,0);}at});

与已经讨论了LAZZY_STATIC和较新的ONCE_CELL的现有答案相比,这个答案没有提供任何新的东西。在SO上将事物标记为重复的目的是为了避免冗余信息。-约翰·谢普马斯特

点击“发布您的答案”,即表示您同意我们的服务条款、隐私政策和Cookie政策。

不是你想要的答案吗?浏览标记的其他问题或提出您自己的问题。