Why Fn* trait can cause unsoundness in safe Rust?

I'm curious about these examples in

  1. Soundness regression with `for<'lt>` closure having an implied bound from return type · Issue #114936 · rust-lang/rust · GitHub,
  2. `&'a T` → `&'static T` in safe code · Issue #118876 · rust-lang/rust · GitHub,
  3. and the famous Implied bounds on nested references + variance = soundness hole · Issue #25860 · rust-lang/rust · GitHub

these unsound examples seem to be almost associated with the Fn* series traits. However, I don't understand the reason, IMO, are they caused by we do not specify the explicit lifetime trait bound? And the compiler just oversight to check the necessary lifetime trait bounds on which the well-formed example needs?

How to interpret the reason in short? BTW, these issues seem to be hung up for a long time, are these bugs possibly fixed?

Yes.

Yes. A PR was raised hours ago.

The PR seems to not mention the famous 25860, what is the difference between 25860 and the mentioned two issue the PR fix

Not a Rust expert. IIUC, the PR doesn't fix the ancient infamous 25860 which was not mentioned in it, but does fix the other two issues which are the unsoundness regression starting from 1.65.0.
I.e. the ancient one is always unsoundly accepted, while the other two were soundly rejected before 1.65 but become unsoundly accepted after that.

So, I'm concerned whether 25860 can be fixed.

25860 can be fixed, and is not fixed.

Can it be fixed without modifying the Rust language? With a fix, will some sound programs currently accepted by the unsound Rust compiler need to be changed?

From the blogpost Diving Deep: implied bounds and variance :3 by lcnr, any fix may cause breakage.

So I guess the status is that we're slowly getting there but it is very hard, especially as we have to be incredibly careful to not break backwards compatibility.
src: lcnr's comment

solution1: Add where bounds to binders

// unsound code
for<'out, 'input> fn(&'out (), &'input T) -> (&'out &'input (), &'out T)

// fix up
for<'out, 'input> where<'input: 'out> fn(&'out (), &'input T) -> (&'out &'input (), &'out T)

Rust will have to support for<'out, 'input> where<'input: 'out> (or more general extended_hrtbs I think), which is challenging because

I (the author, lcnr) don’t see how this approach won’t break at function boundaries.
Implementing this without a noticeable performance impact or without missing some suble edge cases is definitely also a challenge.

solution2: Make lifetimes mentioned in implied bounds early-bound

// unsound code
fn foo<'out, 'input, T>(_dummy: &'out &'input (), value: &'input T) -> &'out T

// fix up: note the explicit `'input: 'out` bound
fn foo<'out, 'input, T>(_dummy: &'out &'input (), value: &'input T) -> &'out T
where
    'input: 'out

While I assume the breakage of this to be acceptable, we won’t know that for sure until we try.

solution3: solution2 and then solution1

I personally would like to see some experimentation with forcing lifetimes mentioned in implied bounds to be early-bound and only consider adding where bounds to binders if the breakage from this change is too great.

6 Likes

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.