Skip to content
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

Getting the address of an error can be used to ignore error handling #9931

Open
sagehane opened this issue Oct 11, 2021 · 5 comments
Open

Getting the address of an error can be used to ignore error handling #9931

sagehane opened this issue Oct 11, 2021 · 5 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@sagehane
Copy link
Contributor

Problem:

Prepending _ = & to an error value can be used to skip error handling. This didn't seem like intended behavior.

How to reproduce:

Sample test.zig file:

const std = @import("std");
const stdout = std.io.getStdOut().writer();

fn returnError() !void {
    return error.@"Successfully returned an error!";
}

pub fn main() void {
    _ = &returnError();
    _ = &stdout.print("Hello world!\n", .{});
}

Output:

$ zig run test.zig
Hello world!

Notes:

Tested on 0.9.0-dev.1343+75cecef63.

I would think the proper way to get this kind of behavior would be to do:

returnError() catch {};

I have no idea what the correct approach to fixing this should be, but it didn't seem right.

@N00byEdge
Copy link
Contributor

I mean, getting the address of a temporary should probably be a entirely different error to begin with

@mikdusan
Copy link
Member

It is status-quo the compiler does not force use of try:

const std = @import("std");

pub fn main() void {
    const x = foo(); // @TypeOf(x) == FooError!void
    std.debug.warn("--> {}\n", .{x});
}

const FooError = error { Yikes };

fn foo() FooError!void {
    return error.Yikes;
}

@sagehane
Copy link
Contributor Author

sagehane commented Oct 11, 2021

But wouldn't this be more comparable?

const std = @import("std");

pub fn main() void {
    _ = foo(); // @TypeOf(x) == FooError!void
}

const FooError = error{Yikes};

fn foo() FooError!void {
    return error.Yikes;
}
$ zig run test.zig
./test.zig:4:12: error: error is discarded. consider using `try`, `catch`, or `if`
    _ = foo(); // @TypeOf(x) == FooError!void

I'm essentially discarding the error.

Edit:

Even this would be an error too:

// Omitted code

pub fn main() void {
    const x = foo(); // @TypeOf(x) == FooError!void
    _ = x;
}

@mikdusan
Copy link
Member

tbh I'm not sure what the reasoning is behind _ = generating error is discarded

@rohlem
Copy link
Contributor

rohlem commented Oct 11, 2021

not sure what the reasoning is

Here's my attempt at an explanation:
Calling a void function works:
foo();
If the function is changed to return a value, it's assumed that value holds some significance. (F.e. the often-ignored int result of C's printf.)
Therefore, you now need to opt out of using the value:
_ = foo();
If the function is further modified to change its result to an error union, now only callers that previously used the value will notice. (It would have been an error to write try beforehand.)
This is (probably) why the compiler reports an error, and you have to provide some sort of error handling strategy:
foo() catch {}; for !void, _ = foo() catch undefined; to discard other values, or something safer like _ = try foo();

From what I understand, the compiler probably decides to emit the error based on the discarded expression's type, in which case that could be extended to pointer-to-single-error-union.
You could probably construct some weird edge cases in generic code that would suffer from this, not sure.

It might be easier to special-case taking the address of function-returned error unions. I think introducing a temporary variable is a workable workaround, if that were the intended behaviour.


getting the address of a temporary should probably be a entirely different error to begin with

There is an error planned for returning a dangling pointer to to-be-destroyed stack space: #2646

Outdated: However, I don't think that's what's happening here. Afaik, current language semantics are to extend the lifetime of objects involved with address-of-expressions... I think until the end of the function, but I might be wrong. This is similar to [compound literals introduced in C99, which give automatic storage duration until the end of the current block](https://en.cppreference.com/w/c/language/compound_literal).

I think this was introduced some years back to make the std.mem.Allocator-like interface pattern of returning a pointer to an object-embedded v-table harder to get wrong, although I don't think it was ever well-documented, and I'm kind of hesitant to use it anyway.

EDIT: For anyone reading this now, I think I've read in some stage2 discussion that the automatic lifetime extension I originally mentioned above has already been scrapped. So avoid dangling pointers.

@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Nov 20, 2021
@andrewrk andrewrk added this to the 0.10.0 milestone Nov 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

5 participants