#defer and errdefer
1 messages · Page 1 of 1 (latest)
errdefer and defer execute starting from the latest one, to the earliest one
so e.g., a defer at the beginning of the scope will run last, and the defer in the last bit of the scope will run first
it's kind of like how RAII works
just more general
and not tied to types
also, yes
you can both defer and errdefer
there's no restrictions on how you can use them
So if I do
defer stdout.print("A", .{});
defer stdout.print("B", .{});
errdefer stdout.print("C", .{});
defer stdout.print("D", .{});
and i return a error, the output would be DCBA?
or CDBA?
without an error, that would be DBA, and with an error, that would be DCBA, yes
okay, so they weight the same
I kknow this is silly, but can I do a return in a defer or errdefer?
no
figured, just checking >P
they run after the return
so e.g.,
var result: u32 = 0;
defer result += 1;
return result;
should return 0
because the deferred expression result += 1 runs after the return, not before
so it wouldn't make a lot of sense to be able to return afterwards
what do you mean?
lets say you are in a function
and you make a variable in that function, assign a value to it, and then return
when you create the variable, it gets pushed in the stack
and when you return from the function, stack unwinding happens, and the stack pointer is moved
so effectivly the variable is no longer there
if you can still reference the variable in the defer, why wont it change the return value?
since you are returning that variable
I imagine in terms of implementation details, it just does
const return_value = expr;
run_defers();
return return_value;
must be something like that yeah, but that could have weird behaviour
not really
what if the thing you are returning cant be copied/moved
I mean, if the thing you want to initialised can't be copied or moved, then initialising with a return statement is the wrong thing to do in the first place
even without any defers, there's not a way to directly refer to the result location of a function call through a return
so self-referential structs and the like have to be initialised via out-parameter
still is weird since you are refering the variable in the defer, so you should be able to... mess with the variable, before you return
I would expect the return to be 1 in your example
I disagree
also, defer is sold as a "cleanup code that runs in the end of the function"
it is
it is designed the way it is to accommodate the clean up patterns in zig
e.g.
var string = std.ArrayList(u8).init(allocator);
defer string.deinit();
// append, resize, and so on
return try string.toOwnedSlice(); // <- this moves the allocation out of the arraylist, making the `defer string.deinit()` a noop
if the defer string.deinit() ran before the return statement here, it would cause a UAF
or a double free, depending on the allocator behaviour
either way, it would be messy
hold up I think i figured it out with it wont work
and severely worsen many APIs
could it be because
the thing you are returning isnt the actual thing you are returning
since its a union
no, it's equally a copy without it being a union
Im guessing that the return statement copies the thing to a union or something that can either be a error type or return type
it would make sense why the defer foo += 1; wouldnt change the value THAT WOULD BE RETURNED because it was changing the variable, but the value in te variable would already be copied to this thing that can either be a error or a value
kinda like what you told above
it doesn't have anything to do with it being a union
it's just copied because that's how return works
it doesn't return a stable memory addressed thing
still i think that can be weird... like what if its a very huge object
it cant copy
it must move
but moving means it would be UB to change the variable value
if it's huge, you probably shouldn't have it on the stack to begin with
well but thats a me concern, not a language concern
there just aren't complex move semantics in zig
there are already complex issues surrounding this topic
i come from c++ and over there you can do pretty insane move stuff
most of that isn't to do with huge structs, and more to do with ownership semantics
this is why I think that, if the return does behave like you explained, it could have some issues
expecially since they say we can use stuff from C++ in Zig
nobody has said that
the creator did, in some talk
you've probably misunderstood
that we can use C includes and C++ includes in zig
no, you can use C includes in zig with translate-c
and if there are C bindings for a C++ library, or your make C bindings yourself for a C++ library, then you can use the C++ library
by the way, I actually do recommend actually reading up on the issue and broader conversation linked by it if you actually want to understand design rationale and current proposals intended to solve current design issues
no
ahh, i see
zig is imperative, and maybe data oriented
so it really is just a modern C?
that is all it is marketed as, yes
ok ok
at any rate, to respond to the idea of it being problematic in the case of huge structs: if your struct is huge enough that copying it would cause stack overflow, having it on the stack in the first place is already going to cause you problems without copying it
so the solution to having a huge struct on the stack is to not have it on the stack in the first place
Yeah, I was just trying to see how that would behave with the C++ functionality
there is no builtin C++ functionality
then don't
I think that Zig should consider that
use an out pointer
well I mean, I might not want to copy more then the thing I rwrite in code
it is considering that, and there's a wealth of conversation on the topic in issues and proposals
and it turns out it isn't that simple
Like I said
if you really want a huge ass struct on the stack and don't want to heap allocate it
only refer to it by pointer, and initialise it through an output pointer
It could be a small struct
I was talking about, the code doing what I write it to do
it does do what you write it to do
well, yes, but more
it can't be "more"
those are just the semantics of the code, and it is what everyone currently using zig expects it to do
your way is less useful, and breaks common patterns already in use
I'm talking about the thing you told, that is, you want to return a variable, it copies that variable (at the return point) to another place, and if you reference the variable in a defer or errdefer (i assume), it will reference your original variable, and any changes made to it wont affect what is being returned)
meaning, if there is something you want to do on the variable before you return it, regardless of the place in code, you must have code before each return stament
because you wont be able to use that in the defer or errdefer
there are no patterns in zig that really necessitate that
if you want the result in my example to always be at least 1, just make it equal to 1 on initialisation
no defer shenanigans needed that way
if you want to assert that the result meets certain condition, you can still do that, since that doesn't require modification at all
zig doesn't do things just because you want to be able to do so
it serves specific, useful use cases
okay this is silly but let's say that you wanted something like this:
fn foo(....) {
var n: i16;
defer n = if(n % 2) 1234 else 4321;
// mess with n
return n;
}
that doesn't seem useful at all
and in this example, you can just but it before the single return statement
that is my point
if I had several return locations
I would need to do that in every on of them
if you happen to have multiple return statements, you can abstract it into a function
and I though the point of defer was to avoid that
the point of defer is to simplify cleanup code
which its current semantics do perfectly
So I should use it exclusivly to clean resusrces used by the function?
Not to run arbitrary code in the end?
it can be used for other purposes, e.g. errdefer |err| assert(diagnostic_ptr == null or diagnostic_ptr.?.err == err);
but it was never designed to run arbitrary code at the end for modifying your return value
I see, is a compiler error generated if I do it?
here's a counter point: what if you only want some return sites to have this behaviour? then you have to modify all your return sites to a different approach, to be able to both do the defer behaviour, plus the behaviour of the new return site
refactoring that would take longer than just having used something like return transformIntoReturn(result); at every call site to begin with
no, you can still do whatever you want in defers and errdefers
zig doesn't have a language-level concept of resources
it's all encoded in the patterns we use
I see
a compiler doesn't recognise if a defer is a cleanup procedure or an assertion or just a variable modification
and modifying variables can actually be a part of a cleanup procedure
e.g., hashmap.deinit() deallocates the hash map, and invalidates the value (sets it to undefined)
some variables, sure, but the one we return it has no effect
the compiler doesn't know that
so isssuing a compile error for that would inevitably create false positives
anyway, this thread has gone on long enough. I highly recommend you read up on the topic
Okay, I will read what you shared with me so far, thank you for replying :)