Skip to content

Unclear unsoundness warning when reborrowing data using late-bound lifetimes across multiple trait definitions #58868

Open
@darkwisebear

Description

@darkwisebear

This is a cross post from the users forum where I was redirected to here:

While working on a project I hit a brick wall when designing a more complicated way to reborrow an object so that a different interface is exposed. This is the definition of the types used (stripped down):

trait WorkspaceLog {
    fn get(&self) -> usize;
}

struct TheLog<'a>(&'a FSWorkspaceController<'a>);

impl<'a> WorkspaceLog for TheLog<'a> {
    fn get(&self) -> usize {
        ((self.0).0).0
    }
}

trait WorkspaceController<'a> {
    type Log: WorkspaceLog+'a;

    fn get_log(&'a self) -> Self::Log;
    fn set_log(&mut self, x: usize);
}

struct FilesystemOverlay(usize);

struct FSWorkspaceController<'a>(&'a mut FilesystemOverlay);

impl<'a, 'b> WorkspaceController<'b> for FSWorkspaceController<'a> {
    type Log = TheLog<'b>;

    fn get_log(&'b self) -> Self::Log {
        TheLog(&*self)
    }
    
    fn set_log(&mut self, x: usize) {
        (self.0).0 = x;
    }
}

trait AsWorkspaceController<'a, 'b> {
    type Controller: WorkspaceController<'b>+'a;

    fn get_controller(&'a mut self) -> Self::Controller;
}

impl<'a, 'b> AsWorkspaceController<'a, 'b> for FilesystemOverlay {
    type Controller = FSWorkspaceController<'a>;

    fn get_controller(&'a mut self) -> FSWorkspaceController<'a> {
        FSWorkspaceController(self)
    }
}

Now, if I directly use the type FilesystemOverlay everything is just fine:

fn init1(control_dir: &mut FilesystemOverlay) -> usize {
    let controller = control_dir.get_controller();
    let log = controller.get_log();
    log.get()
}

However, if I try to generalize control_dir, I get a problem:

fn init2<O>(control_dir: &mut O) -> usize
    where for<'a, 'b> O: AsWorkspaceController<'a, 'b> {
    let controller = control_dir.get_controller();
    let log = controller.get_log();
    log.get()
}

Although this compiles, there is a warning which might eventually be turned into an error:

warning[E0597]: `controller` does not live long enough
  --> src/main.rs:59:15
   |
59 |     let log = controller.get_log();
   |               ^^^^^^^^^^ borrowed value does not live long enough
60 |     log.get()
61 | }
   | -
   | |
   | `controller` dropped here while still borrowed
   | borrow might be used here, when `controller` is dropped and runs the destructor for type `<O as AsWorkspaceController<'_, '_>>::Controller`
   |
   = warning: This error has been downgraded to a warning for backwards compatibility with previous releases.
           It represents potential unsoundness in your code.
           This warning will become a hard error in the future.

It is unclear to me why the compiler thinks that controller is still borrowed and the warning doesn't carry any information which object controller is borrowed to. In addition I wonder why the compiler thinks it's unsound: As log was constructed after controller and may be dropped before since the lifetime of log is late-bound, and the compiler is free to choose a shorter lifetime.

This is the full code on the playground.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-borrow-checkerArea: The borrow checkerA-destructorsArea: Destructors (`Drop`, …)A-diagnosticsArea: Messages for errors, warnings, and lintsD-confusingDiagnostics: Confusing error or lint that should be reworked.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions