Constructors and borrows

Hi,

Consider the snippet

struct A<'a> {
    environment: Environment,
    session: Session<'a>
}
impl<'a> A<'a> {
    fn new() -> Result<Self, Error> {
        let environment = Environment::builder().build()?;
        let mut session = environment.session_from_model("model.onnx")?;
        Ok(A{environment, session})
    }
}

which is a pseudo-code version of me trying to encapsulate an environment and session from the onnxruntime library in a struct, and giving it a constructor. The library is probably irrelevant to the question.

This will of course fail to compile with returns a value referencing data owned by the current function, given that session borrows environment, which is a local variable.

I have been trying for two hours to make it work with different ideas (initialization function instead of a new, storing session and/or environment as Rcs, Options, etc.) but I hit a different problem every time. It seems that I can't overcome the fact that session will borrow environment.

What would be a solution?

This kind of struct declaration with a lifetime parameter 'a means that the contents of session borrow from something outside of the struct A. What you’re trying is called a “self-referential” struct/data type. These aren’t supported in Rust natively (without using e.g. pointers and – crucially – lots of unsafe), but there’s crates such as ouroboros that provide safe macros for doing something like this.

Alternatively, it’s often just avoided to bundle some owned data together with borrowed data based on the owned one. You can also search for the term “self-referential struct” if you want to find out more.

1 Like

Thank you for your reply.
I would be happy to get rid of that lifetime parameter and make everything owned by the struct, though... I just put this lifetime parameter because the compiler complained (heh), given that apparently this type requires a lifetime.

For example

use onnxruntime::{environment::Environment, session::Session, OrtError};
use ouroboros::self_referencing;

pub struct A(AInner);

#[self_referencing]
struct AInner {
    environment: Box<Environment>,
    #[covariant]
    #[borrows(environment)]
    session: Session<'this>,
}

type Error = OrtError;
impl A {
    pub fn new() -> Result<Self, Error> {
        Ok(A(AInner::try_new(
            Box::new(Environment::builder().build()?),
            |environment| {
                environment
                    .new_session_builder()?
                    .with_model_from_file("model.onnx")
            },
        )?))
    }
}
1 Like

You could alternatively lift environment construction out of the constructor for A, so the constructor would then look like:

struct A<'env> {
    environment: &'env Environment,
    session: Session<'env>,
}

impl<'env> A<'env> {
    fn new(environment: &'env Environment) -> Result<Self, Error> {
        let session = environment.session_from_model("model.onnx")?;
        Ok(A{ environment, session })
    }
}

You would then be required to make sure that the outer Environment that A is borrowing from outlives A.

3 Likes

Thanks a lot for your help!

Good catch. Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.