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

Proposal: Remove the ambiguity of forced comptime types #5672

Open
2 tasks
cristobal-montecino opened this issue Jun 23, 2020 · 4 comments
Open
2 tasks

Proposal: Remove the ambiguity of forced comptime types #5672

cristobal-montecino opened this issue Jun 23, 2020 · 4 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@cristobal-montecino
Copy link

cristobal-montecino commented Jun 23, 2020

The principal reason of this proposals is to preserve the symmetry between compiled and interpreted code.

  • Force to add comptime to the arguments of a function that returns a forced comptime type.
  • Remove the ambiguity between var and forced comptime var.

Forced comptime type

Definition

I doesn't know how is called that types; but I refer to types such that:

T is a forced comptime type iff

fn compiledFunction() {
   var checkIsForced: T = undefined;
}

Throw the compiler error:

error: variable of type 'T' must be const or comptime
    var checkIsForced: T = undefined;
    ^

Examples

For example, type is a forced comptime type, because

pub fn main() anyerror!void {
    var my_type: type = u64;
}
error: variable of type 'type' must be const or comptime
    var my_type: type = u64;
    ^

An other example, Dynamic is a forced comptime type:

const Dynamic = struct { value: var };

pub fn main() anyerror!void {
    var check: Dynamic = undefined;
}
error: variable of type 'Dynamic' must be const or comptime
    var check: Dynamic = undefined;
    ^

Force to add comptime to the arguments of a function that returns a forced comptime type

A function that returns a forced comptime type, only can be called with constants arguments. So it makes sense to force the arguments to be comptime.

For example, here, ConstantType is a function that returns a forced comptime type; in this case, type.

const std = @import("std");

fn ConstantType(arg: [] u8) type {
    return u64;
}

pub fn main() !void {
    const stdin = std.io.getStdIn().inStream();
    const stdout = std.io.getStdOut().outStream();

    var buff = [_]u8 { 0 } ** 100;

    const input = stdin.readUntilDelimiterOrEof(buff[0..], '\n') catch "" orelse "";

    try stdout.print("{}\n", .{ ConstantType(input) });
}
error: unable to evaluate constant expression
    try stdout.print("{}\n", .{ ConstantType(input) });
                                             ^

As expected, shows an error; but the error message doesn't explain the reason behind. So the solution is to change the error message to show that the return type of the function requires that the arguments need to be constants.

The proposal is to change, for example, to

fn ConstantType(arg: [] u8) type {
    return u64;
}
error: returning `type` requires the arguments to be declared as comptime
    fn ConstantType(arg: [] u8) type {
                    ^

Remove the ambiguity between var and forced comptime var

The problem

By definition, a forced comptime type throws an error in a compiled function:

fn compiledFunction() {
   var checkIsForced: T = undefined;
}
error: variable of type 'T' must be const or comptime
    var checkIsForced: T = undefined;
    ^

But, in a interpreted only functions, doesn't:

fn interpretedOnlyFunction() {
   var checkIsForced: T = undefined;
}

For example,

const Dynamic = struct { value: var, };

pub fn main() anyerror!void {
    var check: Dynamic = undefined;
}
error: variable of type 'Dynamic' must be const or comptime
    var check: Dynamic = undefined;
    ^
const Dynamic = struct { value: var };

fn interpretedOnlyFunction(comptime _: var) void {
    var check: Dynamic = undefined;

    @compileLog(check);
}

comptime {
    interpretedOnlyFunction(.{});
}
| undefined

The reason is that var in the interpretedOnlyFunction is implicitly considered as a comptime variable.

Extra

To be much clear that var and comptime var generates different results:

fn isComptime() bool {
    var ambiguity_var = true;

    const x: u8 = 0;
    const y: u16 = 0;
    const value = if (ambiguity_var) x else y;

    return @TypeOf(value) == u8;
}

pub fn main() anyerror!void {
    const at_runtime = isComptime();
    const at_comptime = comptime isComptime();

    @import("std").debug.warn("runtime: {}, comptime: {}\n", .{ at_runtime,  at_comptime });
}
runtime: false, comptime: true

(#868 (comment))

Proposal

So, the proposal is to create comptime var as a token to avoid generate different code with respect a runtime value; consider var of a forced compiled type an error and change the error message, then the only way is to declared as const or comptime var.

This makes more clear the intention and preserve the symmetry between compiled and interpreted code.

Examples:

const Dynamic = struct { value: var, };

pub fn main() anyerror!void {
    var check: Dynamic = undefined;
}
error: variable of type 'Dynamic' must be const or comptime var
    var check: Dynamic = undefined;
    ^
const Dynamic = struct { value: var };

fn interpretedOnlyFunction(comptime _: var) void {
    var check: Dynamic = undefined;

    @compileLog(check);
}

comptime {
    interpretedOnlyFunction(.{});
}
error: variable of type 'Dynamic' must be const or comptime var
    var check: Dynamic = undefined;
    ^
const Dynamic = struct { value: var, };

pub fn main() anyerror!void {
    comptime var check: Dynamic = undefined;
}
const Dynamic = struct { value: var };

fn interpretedOnlyFunction(comptime _: var) void {
    comptime var check: Dynamic = undefined;

    @compileLog(check);
}

comptime {
    interpretedOnlyFunction(.{});
}
| undefined

Maybe comptime var can be used as a hint. For example,

fn isComptime() bool {
    var true_var = true;

    const x: u8 = 0;
    const y: u16 = 0;
    const value = if (true_var) x else y;

    return @TypeOf(value) == u8;
}

pub fn main() !void {
    const at_runtime = isComptime();
    const at_comptime = comptime isComptime();

    @import("std").debug.warn("runtime: {}, comptime: {}\n", .{ at_runtime,  at_comptime });
}
runtime: false, comptime: false
fn isComptime() bool {
    comptime var true_var = true;

    const x: u8 = 0;
    const y: u16 = 0;
    const value = if (true_var) x else y;

    return @TypeOf(value) == u8;
}

pub fn main() !void {
    const at_runtime = isComptime();
    const at_comptime = comptime isComptime();

    @import("std").debug.warn("runtime: {}, comptime: {}\n", .{ at_runtime,  at_comptime });
}
runtime: true, comptime: true

(Note: this last example need to be discussed in a dedicated thread because the reason of the difference is the lazy way that if is evaluated. Remark that the main reason of this proposal is to force the use of comptime var for forced comptime types)

@cristobal-montecino cristobal-montecino changed the title Proposal: remove the ambiguity of forced comptime types and comptime var Proposal: Remove the ambiguity of forced comptime types Jun 23, 2020
@Vexu Vexu added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Jun 23, 2020
@Vexu Vexu added this to the 0.7.0 milestone Jun 23, 2020
@ghost
Copy link

ghost commented Jun 23, 2020

Better error messages are always a good thing, but a function returning a comptime-only type already implies it can only be called at comptime, and it doesn't take much investigation to find any violations. That's just how the language works. I don't think being even more explicit about something you need to understand to use the language anyway is worth the extra verbosity. Furthermore, this would introduce a distinct divide between comptime and runtime code, against the aims of itself.

@cristobal-montecino
Copy link
Author

cristobal-montecino commented Jun 23, 2020

I want to make not distinction in comptime and runtime code

const std = @import("std");

const Dynamic = struct { value: var = undefined, };

fn getValue(num: u64, msg: []const u8) Dynamic {
    var dynamic = Dynamic {};

    dynamic.value = true;
    dynamic.value = num;

    dynamic.value = msg;

    return dynamic;
}

pub fn main() !void {
    std.debug.warn("{}\n", .{ getValue(3,"hello world").value });
}

if you copy the body of getValue to the main, the code need to change more than the naive:

const std = @import("std");

const Dynamic = struct { value: var = undefined, };

pub fn main() !void {
    comptime var dynamic = Dynamic {};

    dynamic.value = true;
    dynamic.value = 3;

    dynamic.value = "hello world";

    std.debug.warn("{}\n", .{ dynamic.value });
}

You need to add comptime in this case because dynamic variable is forced to be compile-time known.

I want the compiler to force the use of some comptime notation (const or comptime var) in any case.

click to expand to the proposal solution
const std = @import("std");

const Dynamic = struct { value: var = undefined, };

fn getValue(comptime num: u64, comptime msg: []const u8) Dynamic {
    comptime var dynamic = Dynamic {};

    dynamic.value = true;
    dynamic.value = num;

    dynamic.value = msg;

    return dynamic;
}

pub fn main() !void {
    std.debug.warn("{}\n", .{ getValue(3,"hello world").value });
}

@ghost
Copy link

ghost commented Jun 23, 2020

My bad, misread the code. The perceived semantics mismatch was an artifact of that.

However, I'm still against this. Sometimes being explicit just gets in the way, like it is with async -- just leave it to the compiler to work that out, we don't have to annotate it everywhere.

@fogti
Copy link
Contributor

fogti commented Mar 14, 2022

How would this interact with functions where the return type is generic (that is, computed at comptime), and sometimes is a comptime-forced type? Would it be better or worse?
e.g. compare that to the interaction of if (what) 3 else 5, which, if what is not comptime known, currently appears to require if (what) 3 else @as(u8, 5), because otherwise both branches return comptime_int, and that can't be computed at comptime if what is runtime-dynamic.

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

4 participants