2

How to properly initialize a struct in Rust, with good enough encapsulations?

Or more naively:
how to leverage object/instance methods in the initialization/constructing process of structs?

For example, as the initialization block in Kotlin:

private class BinaryIndexedTree(nums: IntArray) {
    private val nNums = nums.size
    private val fenwick = IntArray(nNums + 1) { 0 }

    // where to put this block in Rust?
    init {
        for (idx in nums.indices) {
            update(idx, nums[idx])
        }
    }

    fun update(index: Int, value: Int) {
        var idx = index + 1
        while (idx <= nNums) {
            fenwick[idx] += value
            idx += (idx and -idx)
        }
    }

    fun query(index: Int): Int {
        var sum = 0

        var idx = index + 1
        while (idx > 0) {
            sum += fenwick[idx]
                idx -= (idx and -idx)
        }

        return sum
    }
}

According to Rust Design Patterns, there is no regular constructors as other languages, the convention is to use an associated function.

Correspondingly, in Rust:

struct BinaryIndexedTree{
    len_ns: isize,
    fenwick: Vec<i32>,
}

impl BinaryIndexedTree{
    pub fn new(nums: &Vec<i32>) -> Self{
        let len_ns: usize = nums.len();
        let fenwick: Vec<i32> = vec![0; len_ns + 1];
        for (idx, num) in nums.iter().enumerate(){
            // how to leverage `update()` for initialization
            // update(idx as isize, num);
            // or even earlier: where/how to put the initialization logic?
        }
        Self{
            len_ns: len_ns as isize,
            fenwick,
        }
    }
    pub fn update(&mut self, index: isize, value: i32){
        let mut idx = index + 1;
        while idx <= self.len_ns{
            self.fenwick[idx as usize] += value;
            idx += (idx & -idx);
        }
    }
    pub fn query(&self, index: isize) -> i32{
        let mut sum: i32 = 0;
        let mut idx = index + 1;
        while idx > 0{
            sum += self.fenwick[idx as usize];
            idx -= (idx & -idx);
        }
        sum
    }
}

Is there any way to properly leverage the update method?
As a rule of thumbs, how to properly handle the initialization work after the creation of (all the fields of) the struct?

The builder pattern is a way to go, which introduces much more code just for initialization.

1
  • 1
    Nitpick: don't take &Vec, take &[T]. Commented Aug 1, 2022 at 7:14

1 Answer 1

4

Yes, you can construct the struct then call a function on it before returning it. There is nothing special about the new function name or how the struct is constructed at the end of the function.

pub fn new(nums: &Vec<i32>) -> Self {
    let len_ns: usize = nums.len();
    let fenwick: Vec<i32> = vec![0; len_ns + 1];

    // Construct an incomplete version of the struct.
    let mut new_self = Self {
        len_ns: len_ns as isize,
        fenwick,
    };

    // Do stuff with the struct
    for (idx, num) in nums.iter().enumerate(){
        new_self.update(idx as isize, num);
    }

    // Return it
    new_self
}
Sign up to request clarification or add additional context in comments.

Comments

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.