Initializing a vector in a struct


#1

I’m trying to create a vector that is initialized by a function when a struct is created. Here is my (possibly pathetic) attempt:

struct SqrtRepeatGen<'a> {
    start: u64,
    repeat_vec: &'a Vec,
}

impl<'a> SqrtRepeatGen<'a> {
    fn new(s: u64) -> SqrtRepeatGen<'a> {
        SqrtRepeatGen { 
            start: (s as f64).sqrt() as u64, 
            repeat_vec: SqrtRepeatGen::build_repeat_vec(s, &mut Vec::::new()),
        }
    }
    
    fn build_repeat_vec(sqrval: u64, v: &'a mut Vec) -> &'a Vec {
        let start = (sqrval as f64).sqrt() as u64;
        let mut adder = start;
        let mut denom = sqrval - adder * adder;
        loop {
            if denom == 0 {
                break;
            } else if denom == 1 {
                v.push(start + adder);
                break;
            } else {
                let next_value = (adder + start) / denom;
                v.push(next_value);
                adder = next_value * denom - adder;
                denom = sqrval - adder * adder;
            }
        }
        v
    }
}

Here is the error I’m seeing:

src/main.rs:48:65: 48:82 error: borrowed value does not live long enough
src/main.rs:48             repeat_vec: SqrtRepeatGen::build_repeat_vec(s, &mut Vec::::new()),
                                                                               ^~~~~~~~~~~~~~~~~
src/main.rs:45:41: 50:6 note: reference must be valid for the lifetime 'a as defined on the block at 45:40...
src/main.rs:45     fn new(s: u64) -> SqrtRepeatGen<'a> {
src/main.rs:46         SqrtRepeatGen { 
src/main.rs:47             start: (s as f64).sqrt() as u64, 
src/main.rs:48             repeat_vec: SqrtRepeatGen::build_repeat_vec(s, &mut Vec::::new()),
src/main.rs:49         }
src/main.rs:50     }
src/main.rs:45:41: 50:6 note: ...but borrowed value is only valid for the block at 45:40
src/main.rs:45     fn new(s: u64) -> SqrtRepeatGen<'a> {
src/main.rs:46         SqrtRepeatGen { 
src/main.rs:47             start: (s as f64).sqrt() as u64, 
src/main.rs:48             repeat_vec: SqrtRepeatGen::build_repeat_vec(s, &mut Vec::::new()),
src/main.rs:49         }
src/main.rs:50     }

#2

I think you want

struct SqrtRepeatGen {
    start: u64,
    repeat_vec: Vec<u64>,
}

Your new function does not take any references as argument, so the only way it can create a &Vec is by returning a reference to some local variable. But this reference will become invalid as soon as you return from new, so you can’t store it in the SqrtRepeatGen result.


#3

Thanks for your input matklad. I tried it the way you illustrate and the problem I ran into was that I want the vector to be private to the struct (not passed in). Where do I create the vector? How do I call the initialization function?

The code I included above tries to create the vector in the call to the initialization function (build_repeat_vec) and then return a reference to the same vector.


#4

The code I included above tries to create the vector in the call to the initialization function (build_repeat_vec) and then return a reference to the same vector.

You can certainly do this if you store a vector by value:

struct S {
    xs: Vec<i32>
}

impl S {
    pub fn new() -> S {
        let xs = Vec::new();
        S::init_xs(&mut xs);
        S { xs: xs }
    }

    fn init_xs(xs: &mut Vec<i32>) {
        ...
    }
}

But please note that you can’t really make something “private to the struct”. The privacy boundary in Rust is mod. But of course you can place your struct in a separate mod.


#5

If we add privacy to the mix, I think it should look like this

mod foo {
    mod bar {
        // struct is pub, but the field is not
        pub struct S { xs: Vec<i32> }

        impl S {
            pub fn new() -> S { S { xs: make_xs() } }
        }
        
        // A free standing private function.
        fn make_xs() -> Vec<i32> { ... }
    }

    // Here, you can use `bar::S` and `bar::S::new`.
    // `bar::make_xs` is hidden.

    // You can even
    // pub use self::bar::S;
    // to re-export the S
}

#6

Thanks again @matklad for the education! I started back over from scratch and followed your first piece of advice, simply creating the vector inside the initialization function and returning it by value. I assume this does some sort of cloning operation? Anyway, here’s where I am, this appears to do what I want:

struct SqrtRepeatGen {
    start: u64,
    repeat_vec: Vec,
}

impl SqrtRepeatGen {
    fn new(s: u64) -> SqrtRepeatGen {
        SqrtRepeatGen { 
            start: (s as f64).sqrt() as u64, 
            repeat_vec: SqrtRepeatGen::build_repeat_vec(s),
        }
    }
    
    fn build_repeat_vec(sqrval: u64) -> Vec {
        let mut v: Vec = vec![];
        let start = (sqrval as f64).sqrt() as u64;
        let mut adder = start;
        let mut denom = sqrval - adder * adder;
        loop {
            if denom == 0 {
                break;
            } else if denom == 1 {
                v.push(start + adder);
                break;
            } else {
                let next_value = (adder + start) / denom;
                v.push(next_value);
                adder = next_value * denom - adder;
                denom = (sqrval - adder * adder) / denom;
            }
        }
        v
    }
}

#7

Not really, it will be a move. In C++, if you receive or return a vector by value, it is copied (modulo RVO and move semantics for anonymous temporaries). That is, a new buffer in the heap is allocated and the elements are copied to it. This is an expensive O(n) operation, and that’s why you don’t usually pass vectors by value in C++, unless you need to copy them anyway.

In Rust, by value means moving. That is, the underlying buffer won’t be copied and no memory allocation will occur. Only the three fields comprising the vector itself (length, capacity and pointer to the buffer) will be copied, and this is a cheap O(1) operation which can be optimized away (I’m not a compiler expert). There is no implicit non-trivial copying in Rust. .clone calls must be explicit.

I think that the only common implicit potentially expensive operation in Rust is automatic calling of .drop. So you should not in general worry about hidden performance problems, because expensive operations are explicit, and you should not worry about use after free and the like because of the type system. Basically, hack without fear :wink: