-
Notifications
You must be signed in to change notification settings - Fork 13k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
'Dropping temporaries' prevents borrow check to succeed in match arm without shared ownership #76149
Comments
As you might notice in the reference, the scrutinee of match isn't a temporary scope, temporary inside |
Thanks for taking the time. I suspected that this might actually be documented behaviour, but hope there is a shared understanding that this behaviour is surprising and undesirable. To the 'user' of the match statement, it does look like it's clear that in the match arm there is nothing with a constructor anymore, it seems like the Result has already been 'destructured'. Also there is no obvious way this can be achieved, as a workaround. Do you see a way to improve the user experience around this issue? Maybe with a compiler error that suggests a workaround in these situations? Maybe there is even a way to change the behaviour of Rustc, very much in the same vein as what happened when NLL was introduced. Match statements suddenly became very intuitive to use. Thanks again for your continued support. |
I am now working around this issue by disabling the drop code of the box by leaking it temporarily. This being possible, to my mind, hints at the current handling of destructors in For the example provided here in this issue, here is the corresponding workaround (playground): struct Owner {
to_be_shared: String,
}
impl Owner {
fn share(&mut self) -> Result<Box<dyn ToString + '_>, ()> {
Ok(Box::new(&self.to_be_shared) as Box<dyn ToString>)
}
fn do_something_else(&mut self) {}
}
fn use_owner(mut owner: Owner) {
// disable drop code by leaking
let _v = match owner.share().map(Box::leak) {
Ok(v) => Ok::<_, ()>(v),
Err(()) => {
owner.do_something_else();
return;
}
}
.map(|leaked| unsafe { Box::from_raw(leaked as *mut _) }); // undo the leak right away, so the drop code runs after the match
}
fn main() {
let owner = Owner {
to_be_shared: String::new(),
};
use_owner(owner);
} |
Not sure the scrutinee temporary case can be improved, but the error message should be more informative at least, reopen for better suggestion. |
Having received some pain from this issue, I should be able to come up with a message that would have helped me. A helpful suggestion would be one that helps working around the issue, and I don't know if the 'leak and unleak' workaround is anything we would want to suggest here due to the need for |
This could be a variation of #47680, but it doesn't compile when using Also note that this is related to the use of Another way to make it work would be to manually drop the value returned from fn use_owner(mut owner: Owner) {
let o = owner.share();
let _v = match o {
Ok(v) => v,
Err(()) => {
drop(o);
owner.do_something_else();
return;
}
};
} or fn use_owner(mut owner: Owner) {
let _v = match owner.share() {
Ok(v) => v,
e @ Err(()) => {
drop(e);
owner.do_something_else();
return;
}
};
} |
Thanks a lot! In my case, I could remove the 'leak' workaround and replace it with version 1 of the workarounds above. Much better and certainly something to remember until it is fixed. Maybe it's even possible to turn this into a suggestion, as it seems like the one with the least 'dependencies'. Alternatively, it this one is indeed related to some dark corners of analysis, maybe it's something that should indeed be fixed there. For now it is my hope that people can find this issue when searching, and will be able to help themselves. |
It gets more interesting than that. When trying to use 'owner' once more after the match expression, there seems to be no way to accomplish this as according to borrowcheck, there is still a borrowed value. The usual 'smartness' doesn't seem to apply. The only way I could make this work is by using a scope - the usual explicit struct Owner {
to_be_shared: String,
}
impl Owner {
fn share(&mut self) -> Result<Box<dyn ToString + '_>, ()> {
Ok(Box::new(&self.to_be_shared) as Box<dyn ToString>)
}
fn do_something_else(&mut self) {}
}
fn use_owner(mut owner: Owner) {
{ // this scope is now required to truly sever the link between the locals and `owner`
let res = owner.share();
let _v = match res {
Ok(v) => v,
Err(()) => {
drop(res);
owner.do_something_else();
return;
}
};
}
// this is the added line, which I would expect to work without the extra scope.
owner.do_something_else();
}
fn main() {
let owner = Owner {
to_be_shared: String::new(),
};
use_owner(owner);
} |
I encountered what I think is another instance of this issue and a workaround: struct HoldsReference<'a> {
reference: &'a u32,
}
// must have manual drop impl
impl<'a> std::ops::Drop for HoldsReference<'a> {
fn drop(&mut self) {
unimplemented!()
}
}
struct Error {/* ... */}
fn create<'a>(_reference: &'a u32) -> Result<HoldsReference<'a>, Error> {
unimplemented!()
}
struct Owner {
owned: u32,
}
impl Owner {
// must be `mut self`
fn on_error(&mut self, _error: Error) {
unimplemented!()
}
// All of the following functions seem equivalent but only the last compiles.
fn fails_to_compile_0(&mut self) {
if let Err(err) = create(&self.owned) {
self.on_error(err)
}
}
fn fails_to_compile_1(&mut self) {
match create(&self.owned) {
Ok(_) => (),
Err(err) => self.on_error(err),
}
}
fn fails_to_compile_2(&mut self) {
match create(&self.owned) {
Ok(_) => return,
Err(err) => self.on_error(err),
}
}
fn compiles(&mut self) {
let err = match create(&self.owned) {
Ok(_) => return,
Err(err) => err,
};
self.on_error(err);
}
}
fn main() {} The error message is
In this case the suggestion of adding a semicolon does not help. |
I tried this code (playground link):
I expected to see this happen:
The code compiles. Especially it's possible to call
owner.do_something_else()
in the match branch dealing with the error, which does not borrow fromowner
.Instead, this happened:
According to The Rust Reference:Destructors I believe the drop scope should be relative to each match arm, not according to the outer scope. It appears that Rust still wants to drop the
Result<_, _>
which is already dissolved in the match arm.Also, if no destructor is involved, the code compiles as expected.
The issue first occurred in
gitoxide
, and here is theworkaround.Meta
rustc --version --verbose
:rustc +nightly --version --verbose
:The text was updated successfully, but these errors were encountered: