-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
result location mechanism (previously: well defined copy eliding semantics) #287
Comments
Let me elaborate on a specific usecase: fn foo() -> BigStruct {
const a = BigStruct { ... }; // fine so far
return a; // ERROR: cannot copy type BigStruct
} The reason for this error is that at the time when you declared I'm hesitant to suggest that this rule be well-defined, because it's a bit more demanding of the compiler, and the rules for what is allowed and what's not allowed get more complicated as well. For example: fn foo() -> BigStruct {
const a = BigStruct { ... };
if (something()) return a; // ERROR
const b = BigStruct { ... };
if (something()) return a; // ERROR
if (something()) return b; // ERROR: but really if this were deleted, then all the errors go away.
return a; // ERROR
} However, one argument in favor of this idea is #286, which wants to refer to the return value of a block by name. Among the proposals in that issue, there is a simpler proposal, which is the one in this comment. fn main() {
const a : BigStruct = {
const result = BigStruct{ ... };
result.method();
result // here's the "copy" that could be elided if the compiler notices
// that this block only returns that local variable.
};
} |
Why not just be explicit about it and let the user provide a pointer to the function? Then everyone can clearly see it doesn't get copied and nobody has to wonder why you can 'copy' this non-copyable struct. |
That would require that the user declare the variable on a separate line and initialize it to That all definitely works ok, and it's what you do in C, but it seems more elegant to make the function look like it's returning the thing. However, I agree that the copy-or-not semantics are a little confusing when they're completely implicit, especially when the return type is generic. Then a single function can do and not do the secret pointer thing depending on the type parameters. It is desirable that we only have one obvious way to return things from functions. But if under the hood there are actually multiple ways, we need to be careful that surprises don't break anything. For example, we need to be careful that this doesn't cause any aliasing footguns. |
Something like this is still planned, but this proposal is old enough now that it needs revisiting and reworking before it's ready to be implemented. |
I don't believe named return types need/ should be part of this proposal because cpp has guaranteed copy elision as well and does not have named return types so it seems to be unnecessary. It seems though as if cpp has cases where its not guaranteed (even cpp 17) so it might be worth investigating this before making a final judgement. My cpp is currently not good enough to easily judge the current state of copy elision in cpp. |
Here is my new proposal for guaranteed copy elision: const Foo = struct {
x: i32,
ptr: *i32,
fn init(z: i32) !Foo { // same function signature syntax
try somethingThatCanFail(); // try still works
@result() = Foo{ // new builtin function which is a reference to the return value
.x = 1234,
.ptr = undefined,
};
if (z == 0) return error.Bad;
if (z == 1) {
// this still works, but doesn't have guaranteed
// copy elision semantics.
return Foo { .x = 0, .ptr = undefined};
}
// in case of error inference, @result() refers to the bare value
@result().ptr = &foo.x;
return @result(); // returning @result() is guaranteed not to copy any memory
}
const Error = error{Bad};
fn init2(z: i32) Error!Foo {
@result() = Foo{
.x = 1234,
.ptr = undefined,
};
// since the result type is fully specified, we need to unwap to get the bare value
const res = &(@result() catch unreachable);
res.ptr = &foo.x;
return @result();
}
}
// works at global scope too
const foo = Foo.init(2);
test "pointer value correct" {
assert(foo.ptr == &foo.x);
} The followup proposal would be something like #591 (comment) where a field could be fixed, and not doing this |
What happens here:
I think this should generate a compiler error? |
(Note in the above I "fixed" the foo.ptr assignment). Also, Is there a simple syntax to something like:
(Sorry for the editing :( ) |
OK I'm back with an updated proposal. I'm confident about this one. So confident, in fact, that I'm going to accept it as the null hypothesis. Everyone is of course welcome to provide alternative proposals or point out flaws in this one that mean it should not be accepted. Copy Elision Part 1, a prerequisite, is well underway in #1682. This proposal is for for Part 2 where we make it possible for functions to return large structs with no copying, guaranteed, and more importantly, to use the return value before returning it, e.g. calling a method on it. I started typing up this complicated proposal and then changed my mind at the end, and here's where I've arrived, somewhere very close to what @thejoshwolfe originally proposed.
|
Accepted Proposal
Old Proposal:
Have well-defined rules for copy eliding, and we sometimes allow what looks like copying non-copyable objects.
var
orconst
declaration creates a location, and passes that location to the initializer expression, if any.+
,~
) creates a temporary storage location for each of its parameters/operands and provides that temporary storage as the result location when evaluating each parameter/operand expression.;
in a block gets a void result location.Examples:
Relative to what #83 originally proposed, we've got relaxed restrictions on returning non-copyable types from a function. Previously returning non-copyable types required use of a named return value. So do we still need named return values?
Here's a usecase for named return values:
We want to design
PluginRegistry
to use the constructor-like pattern where you can assign frominit()
, and we want to do something non-trivial with the object before we return it. In order to refer to the object, it has to be named; we wouldn't be able to callregister()
if we did areturn PluginRegistry { ... }
expression.The text was updated successfully, but these errors were encountered: