How to get the equivalent of C++ initializer lists

2019-08-13 02:13发布

问题:

I have two simple structures like that:

struct Foo {
}

struct Bar<'a> {
    foo: &'a mut Foo;
}

impl Foo {
    pub fn new() -> Foo {
        Foo
    }
}

impl<'a> Bar<'a> {
    pub fn new(foo: &'a mut Foo) -> Bar<'a> {
        Bar {
            foo: foo,
        }
    }
}

So basically the structure Bar needs a reference to a Foo to work on. This is how I use it:

let mut foo = Foo::new();
let mut bar = Bar::new(&mut foo);

Now I want to create a "glue" structure to handle the creation of all these Foos and Bars. So I just want the structure Container to have 1 Foo, 1 Bar.

I tried doing something like:

impl Container {
    pub fn new() -> Container {
        Container {
          foo: Foo::new(),
          bar: Bar::new(&mut foo),
        }
    }
}

But it doesnt work, I cannot reference the field foo from the bar initializer. I basically want to mimic the following C++ code:

Container::Container() :
    foo(),
    bar(&foo)
{}

Any ideas on how to solve this? Of course using any kind of dynamic allocation/reference counting would be highly overkill here.

--- EDIT ---

To be clear, I want to create the Rust code equivalent to this C++ snippet:

struct Foo {
};

struct Bar {
    Bar(Foo& f) : foo(f) {}
    Foo& foo;
};

Foo f;
Bar b(f);

struct Container {
    Container() : f(), b(f) {}
    Foo f;
    Bar b;
};

--- EDIT ---

Here is what I ended up doing, using Rc<>.

use std::rc::Rc;

struct Foo {
  pub bar: Rc<Bar>,
}

impl Foo {
  pub fn new(bar: Rc<Bar>) -> Foo {
    Foo {
      bar: bar,
    }
  }
}

struct Bar {
  a: u8,
}

impl Bar {
  pub fn new() -> Bar {
    Bar {
      a: 42,
    }
  }
}

struct Container {
  pub bar: Rc<Bar>,
  pub foo: Foo,
}

impl Container {
  pub fn new() -> Container {
    let bar = Rc::new(Bar::new());
    Container {
      bar: bar.clone(),
      foo: Foo::new(bar.clone()),
    }
  }
}

fn main() {
  // Just checking that we get the same bar for both
  // inside Container and inside Foo
  let c = Container::new();
  println!("{:p}", &*c.bar);
  println!("{:p}", &*c.foo.bar);

  // So from what I understand, here bar is on the stack
  // then byte-copied to the heap where that Rc<> will point?
  let bar = Bar::new();
  println!("{:p}", &bar);
  let foo = Foo::new(Rc::new(bar));
  println!("{:p}", &*foo.bar);

  // Sad story is that using this Rc<> I now use the "cuteness"
  // and safety that I had with reference borrowing:
  // struct Foo<'a> {
  //   bar: &'a mut Bar,
  // }
  // impl<'a> Foo<'a> {
  //   pub fn new(bar: &'a Bar) -> Foo<'a> {
  //     Foo { bar: bar }
  //   }
  // }
  // let bar = Bar::new();
  // let foo = Foo::new(&bar);
}

But this is not really satisfying, I feel like I used a machine gun to kill a rabbit. Any insight greatly appreciated :(

回答1:

Consider this simplified view of memory. I'm making up sizes and offsets to illustrate the problem, they bear no resemblance to reality.

We start by stack-allocating a brand-new Container with your desired structure at address 80.

80 Container
..   foo: Foo (size 8)
88   bar: Bar
       foo: &Foo (size 8, value 80)

Now we pass the structure by value into a method, or return it from where we created it, like a constructor would. Moving by value involves a bit-for-bit copy, so let's move it to address 40:

40 Container
..   foo: Foo (size 8)
48   bar: Bar
       foo: &Foo (size 8, value 80)

Uh oh! The inner foo now points to a piece of memory that isn't part of our struct anymore! That's the type of unsafe code that Rust tries to prevent.

Conceptually, there are some ways to prevent this. Rust could track that a pointer is really just some offset into itself and then rewrite the value every time the item is moved. I don't know, but that seems expensive. It might also need some special syntax to denote it.

A solution I would like to see would involve indirection and the heap. If we heap-allocated the top-level foo and if Rust tracked stack and heap lifetimes separately, then we could have something like:

== stack
80 Container
..   foo: Box<Foo> (size 8, value 10)
88   bar: Bar
       foo: &Foo (size 8, value 10)

== heap
10 Foo (size 8) 

Moving the structure would change the addresses, but the value would be safe-and-sound because Foo is off in the heap and not moving around.

I don't know that there are any plans to support this kind of solution, but I don't think there's been much clamor for it, either. Maybe it's not even technically feasible!



回答2:

It is not possible to express this concept in safe code in Rust.



标签: rust