Lt<'a> lang item (because we don't have DerefMove) - lifetimes for fn!

I do agree for impl Fn() -> Lt<'a> but not for the simple function type. Panicking was more or less meant as an example merely for sake of proving outright that this type is always inhabited regardless of the original lifetime bound, thus making the relaxation useful. A pure function pointer simply can not contain any state itself currently, so there is nothing that could become invalid. However it conjures up (or doesn't) the return type is an entirely separate problem. Note also that for co-variant return types (e.g. &'a T is a subtype of &'static T) it is entirely feasible to write a 'static function returning by having it return the 'static version. Since there is no lifetime-dispatch in the type system such a function is compatible with any 'smaller' output lifetime.

Also, I'm not arguing for changing any variance on functions or even making it bivariant. I'm arguing for treating them as an actually empty type instead of one containing PhantomData<(T0, … Ti)> for the purpose of checking lifetime bounds, not for subtyping relationships. That's a bit weird I assume (and probably somewhat complicated to fully establish in theory) but it can be solved by adding a deduction rule, not restricting any currently.

So you want Rust to support JITs from 1.0 up to the next edition boundary? Removing JITs from the ecosystem is useful because...?

According to your definition, you cannot return a value with a lifetime less than 'static from a function with an unbound lifetime. This means that the return type is lying - it's return type is not what it is defined to be. I think it's reasonable that unsafe code expect it can return any correct (valid and satisfying any user-defined safety invariants of the type) value of the return type from an fn-ptr (barring any additional requirements it must uphold). If it returns a &'a i32, it should be able to return any &i32 borrowed for at least 'a, and, in particular, should be able to return at any time the function is called provided such a valid value can be produced (so fn()->! cannot return, but fn()->() can). Further, as mentioned, even if it could not possibly produce a &'a i32, you cannot statically call it outside of 'a, because then you would statically hold a &'a i32 outside of 'a lifetime - a borrowck violation.

These two are subtype related fn() -> &'static str <: fn() -> &'a str by not my definition but the accepted one of covariance in output types. All static references are valid as 'a reference, so all functions of the former are valid as functions of the latter type. That makes it, in theory, perfectly okay to turn the former into the latter.

In other words, it is possible to implement functions with such output types simply by overgeneralizing.

Perfectly legal typing, but the last is—unnecessarily—not considered 'static.

fn leak(b: Box<u32>) -> &'static mut u32 {
    Box::leak(b)
}

fn assert_static<F: 'static>(_: F) {}

fn main() {
    assert_static(leak);
}

fn duck<'a>(a: &'a ()) {
    let duck: fn(Box<u32>) -> &'a mut u32 = leak;
    // assert_static(duck);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=45046aa23a74b176982c9cd355115adb

Because a valid implementation exists, it overrides others, even if the it is if the other possible implementations require unsafe to achieve?

As I mentioned, unsafe code should have the right to assume it can return any correct value of the return type from a function within it's own restrictions. This is not different from a function returning a u32: I can write one that returns 5, just as much as 4, 3, 2, or any other value between 0 and u32::MAX. It seems unreasonable that I cannot produce a function that returns a &'a i32 that is, in fact, a &'a i32, instead of an &'static i32, even if it is impossible to produce such a function using purely safe code.

1 Like

So you want to make crates that currently rely on this unsound for no good reason?

Okay, I rest my case but your proofs are dubious at best. It should probably go like this:

  1. Rust stably promises you can call function reference (misleadingly called function pointers) from any instance
  2. Calling must protect the validity invariants of the result type, including its lifetime invariants
  3. Follows that the function reference's validity invariants must contain the lifetime invariant of the result type

None of the variance stuff has anything to do with it.

We really should have a list of negative proofs implied by the committed stability promises.

I'm not wanting to do anything to crates. There are plenty of crates relying on incorrect soundness assumptions and enforcing them is not destructive. I was asking for someone to explain the in a more formal step-by-step way based on just the Rust stability promises. If you want to introduce a new type to the language, let alone a lang-item, it's pretty reasonable to want to know how this affects the ability to perform future changes. Given that it seems to be included I'm satisfied with the result, not your explanation of it.

It doesn't even need to be part of the language, you can already use it today with no additional language support. It's just slightly lacking on ergonomics as you can't have DerefMove.

I see that. Now that I'm more convinced of the soundness of the construction it seems very cute. Very good find! Just don't get so personal next time, I ignored your responses for most of the thread as quite some of it was strawman assumptions.

You kept ignoring our responses because... you didn't want to try and explain what you were trying to say? We still don't see how you weren't arguing that existing crates should become unsound.

(We do see that you weren't meaning to do that, but we don't understand what you were saying. Sorry.)

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