-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Modifications to an l-value that an r-value may be referencing #1830
Comments
@josh11b and @mconst and I talked a whole bunch about this. I was really not seeing options that I was happy with or that felt satisfying. My inclination was to proceed with an "unspecified" model above, but allowing diagnosing local modification on a somewhat "best effort" case rather than in a super rigorous way. My hope was to get realistic code built up in Carbon with this model, and then evaluate the practical impact of the risk of bugs and the practical cost of the many different options for introducing safety. The down side is that it is a really expensive experiment to run. I think that left mconst and josh11b quite uncomfortable (and many others as well). After discussing a bunch of other variations on experiments we could run, a specific new idea emerged that I found much more compelling. It is very close to a previous one, but for various reasons was (for me at least) not at all obvious until much later. The key idea is to make the initializer for The result would be:
For expressions used as function arguments, we have a different model:
Basically, function arguments don't require explicit We suspect that this is the right initial position. The very limited scope of function input parameters makes them much less worrying to default into potentially risky reference semantics. But the scope of This doesn't change the semantics of the R-values themselves though -- they still are allowed make copies if legal and useful. Otherwise they behave exactly as a But converting from L-value to R-value may require an explicit The result I think will preserve the useful parts of the experiment I was hoping to run with fairly minimal effect. It's mostly intuition and guess work, but I feel like people will actually add the required We'll also know exactly how to consider analogous changes with arguments if we want to. So I think this gives us a pretty solid direction to explore here w/ a minimal risk of constantly dodging bug risks, but still providing reasonable ergonomics, especially on the interop boundary into C++ where I think I've recalled all of this end state from the discussion correctly, but @mconst, please let me know if I missed anything here. PS / [1]: Regarding the PPS: I think we could re-use |
Thanks for writing this all up! I think this is a big improvement over the previous design -- even if we end up making changes later, this seems usable enough that we could at least try it out for a while and see how it works. I kind of suspect we'll end up wanting to make |
Hmm, if this is just unspecified, does that make it harder for sanitizers to report an error if you accidentally modify an lvalue while it has a live rvalue binding somewhere? I'd kind of like sanitizers to be able to unambiguously report this as illegal, even if they can't prove that your code depends on seeing the old or new value. |
I definitely want this to be OK to sanitize. But I don't think we should introduce any actual UB here, as I don't think there is (nearly) enough optimization benefit to matter. |
Just getting my head around this; in your examples, what is the default behaviour where no initialization specifier(?) is present:
It feels like by-ref-but-the-compiler-will-check-my-homework, as opposed to using |
Basically, yes. |
Makes sense - thanks. One last (honest!) question about function parameters. Is it intended to allow this?:
I could get a similar end result by declaring |
Yup, that's allowed! |
I like it! FWIW it gets a +1 from me. |
Is fn Copy[T](x : T) -> T { return x; }
let safe_copy: T = Copy(escaped); (Modulo any syntax issues) |
At the moment, yes... But I can both imagine changes to the semantics of return that would make this not the case, and I can imagine it being useful purely for syntactic reasons to simplify this to an operator. So it seems good to carve out a super explicit and unambiguous operation here rather than rely on the return semantics. |
Where would we write the keyword in a I wonder if there's a better keyword for the "unspecified" behavior -- |
That's a good question. (In particular, if you wanted a copy so you can mutate the loop variable without affecting the underlying range, you can just use As for the meaning of |
If we go with this approach to We wouldn't need any special rules for |
This idea seems promising, and worth writing up as a proposal. |
We can't really. ;] It's dependent on another one. I'm going to try to fold this one into the values and pointers proposal. |
@chandlerc Do you want an issue to track the need for a decision here, or are you comfortable saying that #821 should address this? |
Let's assume it will for now. Closing this as decided as well. We can always reopen if #821 (or a subsequent proposal) surfaces concerns or changes direciton. |
In (not currently accepted) proposal #821 , a
let
may either store an address (like aconst &
in C++) or a copy of the original value. This leads to questions about the following code:Questions:
a
while there is an outstandinglet
that may be referencing it? Under what circumstances? For example, do we only prevent mutations that can be seen in a local analysis of the function.a
affect the value ofb
?T
that must always be implemented using a const reference?T == i32
, where many people might expectlet
to make a copy?Approaches:
let
. Two variations based on whether local modifications would be diagnosed. It could be an error to depend upon the behavior when the l-value is mutated, and possibly a sanitizer run in debug mode would be allowed to treat any such modification as a bug to be reported.let
and the last usage of that name.let
is conceptually a copy that may be optimized away, but uncopyable types are not supported or have additional restrictions.let
is a copy and for others it is a reference and there are restrictions on usage. Concern is being able to mark types accurately and comprehensively (for example, we might want a generic type to only be marked for certain argument values).One general concern is how this would work with C++ interop. It would be nice to be able to pass a
let
to a C++ function taking aconst &
parameter. Choices: (a)let
s are alwaysconst &
, (b) passing alet
to aconst &
causes thelet
to be "pinned" giving it an address (maybe just for the duration of the call?).The text was updated successfully, but these errors were encountered: