In a previous post I proposed a solution to the Drop
trait for safe partial move. As I gain some supporters I decided to formalise it as two pre-RFCs. This is the first part that propose a new artibute for function items. The second one is comming soon and will talk about the Destruct
trait.
Summary
This rfc introduce an attribute #[match]
for function items which require the function to pattern match on its only argument.
#[match]
fn example(input: Result<String,()>) -> String {
match input {
Ok(s) => s,
() => "",
}
}
Motivation
The motivation to do this is to prevent the function code to access the input object, so the input object is garanteed to be destructed by the function, as a consequence, writing code to access the same object is not possible. This allows a futher step to make the existing Drop
right and less magic. There will be a seperate RFC for it. The purpose of this RFC though, is to make the machemism generic and can be used in more contexts.
Guide-level explaination
The function annotated with #[match]
can only have one argument, so the following is illegal:
#[match]
fn example(i1: i32, i2: Result<i32,i32>); //Error: #[match] function have multiple arguments
The argument can be a pattern match:
#[match]
fn example((i,s): (i32, String)) {...}
In that case, there is no restriction on the function body. However, if the argument is a single variable, the body must start with a pattern match on the argument:
#[match]
fn example1(r: Result<String,()>) {
if r.is_ok() {...} //Error: #[match] function not start with a pattern match
}
fn example2(r: Result<String,()>) {
match r { //Ok, start with a pattern match
...
}
}
fn example3(r: Result<String,()>) {
if let Ok(r) = r { //OK, if let is another kind of pattern match
...
}
}
//The other form of pattern match should also count
fn example4(r: Result<String,()>) {
match &r {...} //Error: #[match] function match on an expression, not the argument
}
fn example5(r: Result<String,()>) {
if let Ok(ref r) = r { //Error: ref is not supported as we expect the argument to be moved
...
}
}
The function body is free to add code after the pattern match. However, we require the match must do move/copy the fields, not using ref
, as we expect the argument to be moved after the pattern match, so the original varialbe is at most a copy only.
This attribute can also apply to the trait defintion. In that case, it means: implementations must also have this attribute. However, implementations are free to add this attribute even it is a trait method that the propotype did not define with this attribute.
trait Trait1 {
#[match] fn method1(r: Result<String,()>) -> Self;
#[match] fn method2(&self);
fn method3(&self);
}
impl Trait1 for () {
#[match] fn method1(r: Result<String,()>) -> Self{
...
}
fn method2(&self) { //Error: missing #[match]
...
}
#[match] fn method3(&self) { //Ok, even it is not declared with #[match]
...
}
}
Reference level explanation
This only adds some checks to be done in the HIR level.
Drawbacks
Increased complexity by introducing a new attribute, and require a new HIR check.
Rationale and alternatives
When defining Drop
it is a challenge to do partial move. And right now, when moving things out from something implements Drop
, we get E0509. To allow defining a better Drop
, we need some special restriction on the method drop
.
The plan to reform Drop
is to seperate its duty into two pieces:
- Release external resources and do what ever the object contract required to do to finalize the main object
- Destruct the object into pieces and drop its child objects
The first part is to be done by the same Drop
trait but it should not do any partial move. The second part will be a new trait (to be proposed in another RFC) that have a method takes the object by value. However, as such an object will not have a drop clue, we must restrict the access of this object to only allow pattern match. All the limitations of this attribute are derived from this requirement.
Restrict only the new trait to use the attribute
This alternative is good enough, as it seems not making much sense to allow this attribute in other context. However, it may make the new trait being too magical. And who know how people will use this feature for good?
Other names
Should the attribute be called #[destruct]
or anything else?
Argument level annotation
This is not supported yet but there are proposals. If possible, we can annotate the argument to be pattern matched with, rather than the function, so we can have multiple argument functions. However, I didn’t see a clear benifit of doing this.
Prior Art
(TODO)
Unresulved questions
Not any yet.