You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
You have a receiver foo typed as uni Foo and call a mutable method bar. Foo.bar is typed as fn mut bar(...) -> Option[uni Something]. The arguments passed are all sendable. At the time of writing, such a call is disallowed because the method requires a mutable receiver, and thus it isn't safe to return Option, because the receiver (foo) may retain references to it.
To work around this, one can sometimes do the following:
let foo = recover {
let foo = recover foo # turn the uni Foo into a Foo
foo.bar(...)
foo # turn the Foo back into a uni Foo
}
This is valid because whatever bar returns won't outlive the recover block, and thus no aliases violate the uniqueness constraints of uni T types.
Applying this pattern manually however is painful/repetitive, so we should provide a way for the compiler to do this automatically. Specifically, I think we can generalize this to the following:
If the receiver is uni T, the arguments are sendable, then we allow non-sendable return types (even for mutable methods) if and only if the return value is not used in any way. Thus, this is valid:
foo.bar(...)
bla
But this is not:
let a = foo.bar(...)
bla
Neither is this (because the value is moved outside of the match arm):
match thing {
case Whatever -> foo.bar(...)
...
}
Within these constraints, we can essentially treat foo.bar(...) similar to the explicit recover block, but without the manually written boilerplate.
yorickpeterse
changed the title
Allow returning non-sendable types on uni T receivers if the value is unused/ignored
Automatically recover unique receivers if unsendable return types aren't used
Oct 12, 2023
In the current type checking setup this is a bit tricky: we perform the uniqueness checks as part of the regular type checker, which does a single pass over each method body from top to bottom. This means that when we encounter some expression foo, we don't necessary know if it's used or not (i.e. if it's assigned, the last expression in a body, etc).
In theory we could special-case this: CheckMethodBody::expression is given a used argument, which is set to true when the expression resides in any other expression that takes ownership of the value (Return, DefineVariable, etc) or is the last expression in the list.
In light of #713, it might however be better to move the uniqueness checks into a separate pass entirely. For that particular issue we need a separate inference pass after type checking anyway, and keeping all uniqueness checking code in one place might make things easier to work with.
It might even be worth performing the checks against MIR instead of HIR. This way we have a much cleaner/simpler IR to work with, and one that's faster to iterate over. We don't need the check to be performed before lowering to MIR either, since MIR's correctness (structurally at least) is not influenced by the uniqueness rules being enforced.
In this setup, the HIR type checker is mainly meant to catch type errors that would result in nonsensical MIR being generated, such as calls to non-existing methods. Further/more complicated analysis would be performed on MIR, which is generally easier/more efficient to work with.
Description
A pattern I've run into is the following:
You have a receiver
foo
typed asuni Foo
and call a mutable methodbar
.Foo.bar
is typed asfn mut bar(...) -> Option[uni Something]
. The arguments passed are all sendable. At the time of writing, such a call is disallowed because the method requires a mutable receiver, and thus it isn't safe to returnOption
, because the receiver (foo
) may retain references to it.To work around this, one can sometimes do the following:
This is valid because whatever
bar
returns won't outlive therecover
block, and thus no aliases violate the uniqueness constraints ofuni T
types.Applying this pattern manually however is painful/repetitive, so we should provide a way for the compiler to do this automatically. Specifically, I think we can generalize this to the following:
If the receiver is
uni T
, the arguments are sendable, then we allow non-sendable return types (even for mutable methods) if and only if the return value is not used in any way. Thus, this is valid:But this is not:
Neither is this (because the value is moved outside of the match arm):
Within these constraints, we can essentially treat
foo.bar(...)
similar to the explicitrecover
block, but without the manually written boilerplate.Related work
Pony basically does this: https://tutorial.ponylang.io/reference-capabilities/recovering-capabilities.html#automatic-receiver-recovery, specifically the following (emphasis mine):
Related issues
The text was updated successfully, but these errors were encountered: