【问题标题】:Dealing with so-called global variables in Rust在 Rust 中处理所谓的全局变量
【发布时间】:2021-12-15 04:21:01
【问题描述】:

我们都知道使用全局变量会导致细微的错误。我需要将 Python 程序迁移到 Rust,尽可能保持算法完整。一旦我证明了 Python-Rust 等价性,就有机会调试和更改逻辑以更好地适应 Rust。这是一个使用全局变量的简单 Python 程序,后面是我不成功的 Rust 版本。

# global variable 
a = 15

# function to perform addition 
def add(): 
    global a
    a += 100

# function to perform subtraction
def subtract(): 
    global a
    a -= 100

# Using a global through functions
print("Initial value of a  = ", a)
add() 
print("a after addition   = ", a)
subtract() 
print("a after subtraction = ", a)

这是一个运行的 Rust 程序,但我无法获取闭包来更新所谓的全局变量。

fn fmain() {
// global variable 
    let mut a = 15;

// perform addition 
    let add = || {
        let mut _name = a;
//        name += 100;  // the program won't compile if this is uncommented
    };

    call_once(add);

//  perform subtraction
    let subtract = || {
        let mut _name = a;
//        name -= 100;  // the program won't compile if this is uncommented
    };

    call_once(subtract);

    // Using a global through functions
    println!("Initial value of a    = {}", a);
    add();
    println!("a after addition      = {}", a);
    subtract();
    println!("a after subtraction   = {}", a);
}

fn main() {
    fmain();   
}

fn call_once<F>(f: F)
where
    F: FnOnce(),
{
    f();
}

我的请求:在 Rust 中重新创建 Python 逻辑。

【问题讨论】:

    标签: rust closures global-variables


    【解决方案1】:

    enumfunction 等无依赖示例。编辑:代码改进,如评论和更正的匹配臂中所建议的那样。

    use std::sync::{Arc, Mutex, Once};
    
    static START: Once = Once::new();
    
    static mut ARCMUT: Vec<Arc<Mutex<i32>>> = Vec::new();
    

    // 作为枚举

    enum Operation {
        Add,
        Subtract,
    }
    
    impl Operation {
        // static change
    
        fn result(self) -> i32 {
            let mut arc_clone = unsafe { ARCMUT[0].clone() };
            let mut unlock = arc_clone.lock().unwrap();
            match self {
                Operation::Add => *unlock += 100,
                Operation::Subtract => *unlock -= 100,
            }
            *unlock
        }
    
    
        // dynamic change
    
        fn amount(self, amount: i32) -> i32 {
            let mut arc_clone = unsafe { ARCMUT[0].clone() };
            let mut unlock = arc_clone.lock().unwrap();
            match self {
                Operation::Add => *unlock += amount,
                Operation::Subtract => *unlock -= amount,
            }
            *unlock
        }
    }
    

    // 作为函数

    fn add() -> i32 {
        let mut arc_clone = unsafe { ARCMUT[0].clone() };
        let mut unlcok = arc_clone.lock().unwrap();
        *unlcok += 100;
        *unlcok
    }
    

    // 作为特征

    trait OperationTrait {
        fn add(self) -> Self;
        fn subtract(self) -> Self;
        fn return_value(self) ->i32;
    }
    
    impl OperationTrait for i32 {
        fn add(mut self) -> Self {
            let arc_clone = unsafe{ARCMUT[0].clone()};
            let mut unlock = arc_clone.lock().unwrap();
            *unlock += self;
            self
        }
    
        fn subtract(mut self) -> Self {
            let arc_clone = unsafe{ARCMUT[0].clone()};
            let mut unlock = arc_clone.lock().unwrap();
            *unlock -= self;
            self
        }
    
        fn return_value(self)->Self{
            let arc_clone = unsafe{ARCMUT[0].clone()};
            let mut unlock = arc_clone.lock().unwrap();
            *unlock
        }
    
    }
    

    // 主函数

    fn main() {
        START.call_once(|| unsafe {
            ARCMUT = vec![Arc::new(Mutex::new(15))];
        });
    
        let test = Operation::Add.result();
    
        println!("{:?}", test);
    
        let test = Operation::Subtract.amount(100);
    
        println!("{:?}", test);
    
        let test = add();
    
        println!("{:?}", test);
    
        let test = 4000.add();
    
        println!("{:?}", test);
    
    }
    

    【讨论】:

    • 您可以为unsafe 块使用较小的范围,如here 所示。这样就不需要使用unsafe 来实际调用Operation::resultadd
    • 为什么要创建一个只包含一个元素的向量?
    • @user4815162342 静态强制类型限制。允许使用列表类型,但不允许使用 Arc。
    • ArcMut 结构真的是多余的......
    • 如果您需要Vec 来使初始化程序工作,标准方法是使用Option 并将其初始化为None。除此之外,您可以使用更少的代码获得仅 stdlib 的可变静态,例如play.rust-lang.org/…
    【解决方案2】:

    你的 Rust 代码没有使用全局变量,a 变量是堆栈分配的。虽然 Rust 并不特别支持全局变量,但您当然可以使用它们。翻译成使用实际全局变量的 Rust,您的程序将如下所示:

    use lazy_static::lazy_static;
    use parking_lot::Mutex; // or std::sync::Mutex
    
    // global variable
    lazy_static! {
        static ref A: Mutex<u32> = Mutex::new(15);
    }
    
    // function to perform addition
    fn add() {
        *A.lock() += 100;
    }
    
    // function to perform subtraction
    fn subtract() {
        *A.lock() -= 100;
    }
    
    fn main() {
        // Using a global through functions
        println!("Initial value of a  = {}", A.lock());
        add();
        println!("a after addition    = {}", A.lock());
        subtract();
        println!("a after subtraction = {}", A.lock());
    }
    

    Playground

    如果您更喜欢使用闭包,您也可以这样做,但您需要使用内部可变性来允许多个闭包捕获相同的环境。例如,您可以使用Cell

    use std::cell::Cell;
    
    fn main() {
        let a = Cell::new(15);
        let add = || {
            a.set(a.get() + 100);
        };
        let subtract = || {
            a.set(a.get() - 100);
        };
    
        // Using a global through functions
        println!("Initial value of a    = {}", a.get());
        add();
        println!("a after addition      = {}", a.get());
        subtract();
        println!("a after subtraction   = {}", a.get());
    }
    

    Playground

    【讨论】:

    • Mutex&lt;u32&gt; 最好是std::sync::atomic::AtomicU32,因为它的开销更少。
    • @Cerberus 在这种情况下是的,但Mutex 更容易概括。我怀疑他真实代码中的 OP 需要比u32 更复杂的东西。
    • 第一种方法比第二种方法更优雅、更易于理解。当两个闭包都需要捕获相同的外部变量时,这里的两个闭包很容易导致混淆。
    • @Windchill 这里没有参数。我只包括了那个版本,因为这是 OP 的尝试之一,以展示它是如何工作的。 OP 也可能需要“作用域”全局变量(类似于 crossbeam 的作用域线程),在这种情况下,基于闭包的方法是合适的。
    猜你喜欢
    • 2020-04-19
    • 2021-07-22
    • 2011-01-10
    • 1970-01-01
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-20
    相关资源
    最近更新 更多