Access struct fields while still initializing

Consider this code:

struct Foo {
    f0: &'static str,
    f1: &'static dyn Fn(),
}

impl Foo {
    fn new() -> Self {
        Self {
            f0: "foo!",
            f1: &|| {
                // How to access the `f0` field here?
            },
        }
    }
}

How do I access the f0 field of the Foo object being initialized in the closure?
Or is that not possible and do I need to work around it by extracting the f0 field into a local and closing over it in the closure (which should be possible in the non-toy example, but is uglier than just accessing the field directly)?

I'd make the closure take a Foo as an argument, and then it can access the field. It can't do so without an argument because the struct may have been moved, so it wouldn't know where in RAM to access the field.

As you say, if the Foo is never modified you could put f0 in a local variable and access that in the closure. But that to me feels dangerous, because a user could modify f0 and then be surprised that the closure does not reflect that change.

At that point I may as well make the closure a method.
The reason I don't do that is that each instance of Foo needs to execute different code in the closure.
Think of the Foo type like "a function on steroids".
In fact the real world equivalent of the Foo type has an impl std::ops::Deref for Foo, allowing Foo instances to be called like regular fns.

That part could be worked around with the Pin infrastructure, at least in theory.

It's not, because the type of f0 is &'static str i.e. it is an immutable string literal.
The value could be replaced, but the value bound to f0 cannot be modified.
The 2 things combining to prevent the replacement are 1. the lack of pub accessors capable of doing that and 2. the fact that the struct Foo is opaque i.e. its fields are private. But that is almost a moot point since Foo is pub(crate) (i.e. crate-internal) anyway.

If that is the case, then why are you trying to borrow the field? Even in theory if you could borrow the f0, you wouldn't be able to modify f0 (because f0 would be shared across the closure and the struct, and shared mutability ids not allowed by default). So you could just capture whatever value you put into the field directly, instead of trying to borrow the field.

I understand that, but my statements still apply. A user who modified f0 (which is a reference) may be surprised that they haven't changed the behavior of the closure.

Perhaps it's useful to note that this code is not exposed directly to other users; instead they'll be using a facade macro_rules! macro built on top.

To derive other information from it at compile time.

Mutation in this context is neither necessary nor desired. It's about accessing data (provided using the macro) at compile time, and then deriving other data from that, also at compile time.
Additionally, the data should be queryable at runtime, hence the inclusion as the field f0.

That is precisely what I am attempting to do: use the data that is specified in f0 in the closure field f1 :slight_smile:
My question is about how to accomplish this, see the top post.

Now I see your confusion. Users cannot modify any fields. The public API makes it impossible. Essentially Foo instances are read-only, executable data/code hybrids, at least from the perspective of a consumer of Foo.

1 Like

Order of assignment to the fields matters, and the borrow checker will let you use variables before you move them into the struct. In general this pattern works:

let value = …;
SomeStruct {
    derived: read(&value), // borrowed
    value: value, // then moved
}

The borrow must be temporary and end before the next line.

'static is very weird here. It's pretty much never used for anything in real code. It's leaked memory, so if you insist, you can leak it like this:

struct Foo {
    f0: &'static str,
    f1: &'static dyn Fn(),
}

impl Foo {
    fn new() -> Self {
        let f0 = "foo!";
        Self {
            f1: Box::leak(Box::new(move || {
                f0;
            })),
            f0,
        }
    }
}

It doesn't access via field, because even though the pointed-to string is static, the address of the field holding the reference isn't guaranteed to be static.

If you need to access the field, then a more realistic design would be:

struct Foo {
    f0: &'static str,
    f1: fn(&Foo),
}

impl Foo {
    fn new() -> Self {
        Self {
            f0: "foo!",
            f1: |ctx| println!("{}", ctx.f0),
        }
    }
}

and if you want support more than just string literals/leaked memory, then:

struct Foo<'a> {
    f0: &'a str, // or even better — f0: String
    f1: fn(&Foo),
}

or

struct Foo {
    f0: String,
    f1: fn(&Foo),
}

impl Foo {
    fn new() -> Self {
        Self {
            f0: "foo!".into(),
            f1: Self::f1, // I assume you need ability to reassign this,
            // as otherwise there's no point having it as a field
        }
    }
    
    fn f1(&self) {
        println!("{}", self.f0);
    }
}
1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.