#Why can't the compiler rebind returned references of things that go out of scope?

28 messages · Page 1 of 1 (latest)

hybrid leaf
#

consider this code:

fn f1() -> &str {
  let s = String::from("amogus");
  s.as_str()
}

fn f2() {
  let string = f1();
  // f2 takes ownership of s
}

Currently this obviously errors, because s stops existing. But what if it didn't? Intuitively in case of such an error it should be possible to simply defer the ownership of the out-of-scope variable to whoever is calling the function. It'd also make passing references beween closures around a lot easier.

There's probably a very good reason this isn't possible, but I'm very curious what that reason is

wild delta
#

that would mean the function has a different ABI

#

that is, the calling code would have to be prepared to receive the String, even though that's not marked in the function signature

#

also, s would have to be allocated in caller-provided space, because moving a value invalidates references to it

#

if it were possible to "defer the ownership of the out-of-scope variable to whoever is calling the function" then this could be made to work. The problem is that it just isn't simple to do that.

#

ownership is not just about arranging so "there is an owner for every value"; it is also about the actual memory used to do it

hybrid leaf
hybrid leaf
#

I wonder if this would be a feature that's even worth implementing

wild delta
#

sure, it would be useful

#

I'm saying it's hard

#

also potentially surprising

hybrid leaf
#

yeah it's different from how most (all?) other languages do it anyway

wild delta
#

like, dropping a String doesn't do anything but deallocate memory, but in general drops can have significant side effects

torpid snow
#

Could make unsafe around user-provided functions even harder to manage, since you may propagate drops out of scope

wild delta
#

and this would allow f1 to cause f2 to do more things

#

yeah

hybrid leaf
#

yeah there's a lot of moving parts to this

wild delta
#

this is one of those things where it sounds simple but you would need to rearrange a lot of the details of the language to make room for it

hybrid leaf
#

I was wondering about this because earlier I was creating some structs in a map on an iterator and then later returning some of their contents, except that was an &[u8] and so the struct and thus this reference would go out of scope

#

so to solve that I just had to store the structs in a vec then call .iter() on that

#

but it felt janky and not super necessary

wild delta
#

btw here's how you could do it explicitly

#

?play

fn f1(out: &mut Option<String>) -> &str {
    *out = Some(String::from("amogus"));
    out.as_ref().unwrap().as_str()
}

fn main() {
    let mut storage = None;
    let string = f1(&mut storage);
    println!("{string}");
}
lucid warrenBOT
#
     Running `target/debug/playground`

amogus
wild delta
#

this is basically what the compiler would have to be doing under the hood if it made your original code work

hybrid leaf
#

yeah makes sense

#

just feels needlessly verbose