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: Make comptime struct fields work like comptime variables #5675

Open
ghost opened this issue Jun 23, 2020 · 10 comments
Open

Proposal: Make comptime struct fields work like comptime variables #5675

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

Comments

@ghost
Copy link

ghost commented Jun 23, 2020

(DRAG IT, DROP IT, FOLD-UNFOLD IT)

#5578 proposes, in my opinion, a very ugly solution to its problem. The same use case could be accomplished by accompanying each value with a comptime giveToken, but then there's the problem of encapsulating this functionality. This could be done very cleanly if structs were allowed to exist partly at comptime, partly at runtime, like so:

// **N.B.**: I'm using #1717 syntax because that proposal has been accepted
// -- this proposal does not depend on #1717

const Box = fn (comptime T: type) type {
    return struct {
        const Self = @This();

        val: T,
        comptime times: comptime_int,

        const init = fn (_val: T, comptime _times: comptime_int) Self {\
            return Self { .val = _val, .times = _times };
        };

        // *Self is a hybrid-time pointer, so this function is executed
        // in hybrid time, just like a function with a comptime variable
        const give = fn (self: *Self) T {
            if (self.times <= 0) @compileError("Gave too many times");
            self.times -= 1;

            return self.val;
        };
    };
};

test "Test 1" {
    var runtime_1: usize = 1;
    var a = Box(usize).init(runtime_1, 2);
    _ = a.give();
    _ = a.give();
    _ = a.give(); // should not compile

    // Different instance, different counter
    var b = Box(usize).init(runtime_1, 2);
    _ = b.give();
    _ = b.give();
    _ = b.give(); // should not compile
}

Such fields would of course inherit most of the restrictions of comptime variables -- i.e. no setting in runtime control flow constructs, no setting to runtime values etc., although non-comptime fields would not have these restrictions. Methods would read and write comptime fields but otherwise run at runtime when appropriate. Obviously such a struct cannot be packed or extern, for the same reason that functions with comptime parameters cannot have the C calling convention.

@DutchGhost
Copy link

DutchGhost commented Jun 23, 2020

This indeed looks clean, and I think I even tried this as a first attempt to basically have instance bases comptime variables, rather than global ones.

This seems very intuitive and simple to understand to me :)

@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
@SpexGuy
Copy link
Contributor

SpexGuy commented Jun 23, 2020

I'm in agreement on this one. This accomplishes a similar feat to the closure-over-comptime-var trick, but is much easier to read and work with. I'm a bit worried about this leading to potential code bloat or compile time bloat, since the type is effectively generic and every call using it ends up generating a new function. But tuples and closure-over-comptime-var have a similar problem, and people seem to be ok with those, so maybe this is ok too.

@SpexGuy
Copy link
Contributor

SpexGuy commented Jun 23, 2020

Types with comptime fields would have some fundamental limitations, which are essentially the same limitations covering pointers comptime vars. The compiler has to be able to track the instance through its entire lifetime. You can't store it somewhere and retrieve it later, unless the storage location is also a comptime var. Like async, these limitations are viral. A struct containing a struct containing a comptime field inherits all of these limits.

@zigazeljko
Copy link
Contributor

Obviously such a struct cannot be packed or extern.

Why not? The proposed comptime fields would exist only at compile time, so they should not affect runtime memory layout in any way.

@ghost
Copy link
Author

ghost commented Jul 17, 2020

#5895 encompasses this. Closing.

@ghost ghost closed this as completed Jul 17, 2020
@ghost
Copy link
Author

ghost commented Dec 12, 2020

The crux of #5895 (#7396) is now accepted, so its individual ideas now make sense on their own. Welcome back!

@ghost ghost reopened this Dec 12, 2020
@andrewrk andrewrk modified the milestones: 0.7.0, 0.8.0 Jan 3, 2021
@andrewrk andrewrk modified the milestones: 0.8.0, 0.9.0 May 19, 2021
@ArborealAnole
Copy link

Types with comptime fields would have some fundamental limitations, which are essentially the same limitations covering pointers comptime vars. The compiler has to be able to track the instance through its entire lifetime. You can't store it somewhere and retrieve it later, unless the storage location is also a comptime var. Like async, these limitations are viral. A struct containing a struct containing a comptime field inherits all of these limits.

What if the user or a struct method specifies the values of the comptime fields when dereferencing pointers to such structs?

@rohlem
Copy link
Contributor

rohlem commented Mar 31, 2024

Here's how this proposal could be implemented in my simple mind:

  • Aggregate types are split into two internally, one of comptime fields, and one of non-comptime fields.
  • A pointer to such a type internally consists of the runtime pointer to the non-comptime fields and a comptime-known pointer to the comptime fields.
    • (As a function argument that internal pointer to comptime-allowed fields acts like it was specified as comptime argument.)
  • By the status-quo rules of comptime var-s, writing to comptime fields (whether directly or through a pointer) at runtime / depending on runtime-branching is a compile error.

I think equivalent behavior is already doable with @typeInfo and @Type in userspace,
but less readable because @Type doesn't support reifying declarations,
and it requires manually splitting arguments and routing field accesses,
all of which add a lot of noise over the proposed language feature.

@TheHonestHare
Copy link

I'd like to point out a completely different usecase that this would also allow: comptime default function parameters.

A somewhat common pattern is to do something like this:

fn foo(args: FooArgs) void {}
const FooArgs = struct {
    a: u32 = 1,
    b: MyEnum = .bar,
    c: u8 = ' ',
};

However, afaik there is no way to do this pattern for comptime arguments. I think this pattern may become ever more common with the addition of decl literals allowing quick shortcuts, and implementing this would allow comptime arguments to participate in this too.

@rohlem
Copy link
Contributor

rohlem commented Aug 31, 2024

However, afaik there is no way to do this pattern for comptime arguments.

@TheHonestHare The same is already possible if you separate comptime and runtime arguments into separate types:

fn foo(comptime c: FooComptimeArgs, r: FooRuntimeArgs) void {...}
test foo {foo(.{}, .{});}

Being able to unify both in a single argument can be helpful, though for more complex uses like generic arguments (dependent fields), reflection on an anytype field is still the most versatile.
And if every callsite can provide its own type, then status-quo constant comptime fields are often sufficient.

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

8 participants