1

We all know that using global variables can lead to subtle bugs. I need to migrate Python programs to Rust, keeping the algorithm intact as far as possible. Once I have demonstrated Python-Rust equivalence there will be opportunities to debug and change the logic to fit Rust better. Here is a simple Python program using global variables, followed by my unsuccessful Rust version.

# 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)

Here is a Rust program that runs, but I cannot get the closures to update the so-called global variable.

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();
}

My request: Re-create the Python logic in Rust.

2 Answers 2

6

Your Rust code is not using global variables, the a variable is stack-allocated. While Rust doesn't particularly endorse global variables, you can certainly use them. Translated to Rust that uses actual globals, your program would look like this:

use std::sync::Mutex;

// global variable
static A: Mutex<u32> = Mutex::new(15);

// function to perform addition
fn add() {
    *A.lock().unwrap() += 100;
}

// function to perform subtraction
fn subtract() {
    *A.lock().unwrap() -= 100;
}

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

Playground

If you prefer to use closures, you can do that too, but you'll need to use interior mutability to allow multiple closures to capture the same environment. For example, you could use a 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

Sign up to request clarification or add additional context in comments.

5 Comments

Mutex<u32> would probably better be std::sync::atomic::AtomicU32, since it has less overhead.
@Cerberus In this case yes, but Mutex is easier to generalize. I suspect the OP in his real code needs something more complex than a u32.
The first approach is way more elegant and understandable than the second one. The two closures here will lead to confusion easily when both of them need to capture the same outer variable.
@Windchill No argument here. I only included that version because that was one of the OP's attempts, in order to show how it can be made to work. It's also possible that the OP wants "scoped" globals (analogous to crossbeam's scoped threads), in which case the closured-based approach would be appropriate.
What if I have a nested closure? I'm using gtk-rs, and you need to put event handlers inside an event handler.
-1

Dependency-less examples as enum and function. EDIT : Code improved, as suggested in comment and corrected match arm.

use std::sync::{Arc, Mutex, Once};

static START: Once = Once::new();

static mut ARCMUT: Vec<Arc<Mutex<i32>>> = Vec::new();

// as enum

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
    }
}

// as a function

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

// as trait

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

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);

}

6 Comments

You can use smaller scopes for your unsafe blocks as seen here. This way it is not required to use unsafe to actually call Operation::result or add.
Why do you create a vector that only ever contains one element?
@user4815162342 statics enforce type restrictions. List types are allowed but Arc is not.
the ArcMut struct is superfluous really...
If you need the Vec for the initializer to work, the standard approach is to use an Option and initialize it to None. Aside from that, you can get a stdlib-only mutable static with much less code, e.g. play.rust-lang.org/…
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.