Fred
Fred

Reputation: 579

Dealing with so-called global variables in Rust

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.

Upvotes: 1

Views: 5231

Answers (2)

user4815162342
user4815162342

Reputation: 155416

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

Upvotes: 6

NaN
NaN

Reputation: 125

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

}

Upvotes: -1

Related Questions