Logicrat
Logicrat

Reputation: 4468

How to manipulate an instance of a mutable struct in Rust - ownership woes

I'm just learning Rust, coming from years of C++, and I'm having some difficulty understanding how to deal with some of the ownership restrictions. Specifically, in this little program, which is just a training exercise, I get two different error messages.

let stat = Stdev::new();

The above statement gets the message: move occurs because stat has type Stdev, which does not implement the Copy trait

    stat.add(x);

The above statement gets the message: stat moved due to this method call, in previous iteration of loop.

Here's the whole program:

struct Stdev
{
    count : u32,
    sum : f64,
    sq_sum : f64
}

impl Stdev
{
    fn new() -> Self
    {
        Stdev{count:0, sum:0.0, sq_sum:0.0}
    }

    fn add(mut self, x : f64)
    {
        self.count += 1;
        self.sum += x;
        self.sq_sum += x * x;
    }
    
    fn stdev(&self) -> f64
    {
        if self.count == 0 { return 0.0; }
        let n = self.count as f64;
        let mean = self.sum / n;
        let variance = self.sq_sum / n - mean * mean;
        variance.sqrt()
    }
}

use rand::Rng;

fn main() {
    let count = 1000;
    let stat = Stdev::new();
    for _ in 1..count
    {
        let x = rng.gen_range(0.0..100.0);
        stat.add(x);
    }
    
    println!("Standard deviation is {}", stat.stdev());
}



So, two questions:
1: How can I initialize the variable stat with an instance of the Stdev struct?
2: How can I modify fields within the struct via a method call without running into ownership problems?

Just as a reference, here is essentially the same logic, in C++ instead of Rust, and this program works just fine. In the following code, the class accum is the equivalent of the Rust struct Stdev.

#include <iostream>
#include <cmath>

class accum
{
    int count;
    double sum;
    double sq_sum;
    
public:
    accum();
    int add(double);
    double stdev();
};

accum::accum()
{
    count = 0;
    sum = 0.0;
    sq_sum = 0.0;
}

int accum::add(double x)
{
    sum += x;
    sq_sum += x * x;
    return ++count;
}

double accum::stdev()
{
    if (0 == count) return 0;
    double mean = sum / count;
    double variance = sq_sum / count - mean * mean;
    return sqrt(variance);
}

double gen_range()
{
    double x = rand();
    x /= RAND_MAX;
    return x * 100.0;
}

int main()
{
    int count = 100;
    accum stat;
    for(int i = 0; count > i; ++i)
    {
        double x = gen_range();
        stat.add(x);
    }
    
    std::cout << "Standard deviation is " << stat.stdev() << "\n";
}

I'm particularly confused about the second error. If stat was moved, where was it moved to? There was no assignment, just a method call. I'm confused. Thanks.

Upvotes: 0

Views: 102

Answers (2)

globalyoung7
globalyoung7

Reputation: 1

You can use Default::default() to initialize.

Comparing ownership transfers in rust and C++

struct Stdev {
    count: u32,
    sum: f64,
    sq_sum: f64,
}

impl Stdev {
    fn new() -> Self {
        Stdev {
            count: Default::default(),
            sum: Default::default(),
            sq_sum: Default::default(),
        }
    }

    fn add(&mut self, x: f64) {
        self.count += 1;
        self.sum += x;
        self.sq_sum += x * x;
    }

    fn stdev(&self) -> f64 {
        if self.count == 0 {
            return 0.0;
        }
        let n = self.count as f64;
        let mean = self.sum / n;
        let variance = self.sq_sum / n - mean * mean;
        variance.sqrt()
    }
}

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    let count = 1000;
    let mut stat = Stdev::new();
    for _ in 1..count {
        let x = rng.gen_range(0.0..100.0);
        stat.add(x);
    }

    println!("Standard deviation is {}", stat.stdev());
}


  • result
Standard deviation is 29.224928446158625

Upvotes: -1

aedm
aedm

Reputation: 6584

Change the add function to take a mutable reference to self instead of moving it:

fn add(&mut self, x: f64)

Using the current declaration, calling add will actually move stat and pass the ownership to the add method. Hence, stat can not be used after that call, but the loop needs it, this is why you get the error.

Your new method is fine.

Upvotes: 1

Related Questions