Skip to content

Namespace of pub const in structs is not obvious #8522

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

Closed
mlawren opened this issue Apr 13, 2021 · 6 comments
Closed

Namespace of pub const in structs is not obvious #8522

mlawren opened this issue Apr 13, 2021 · 6 comments
Milestone

Comments

@mlawren
Copy link

mlawren commented Apr 13, 2021

I would have expected the parse member of Interface below to be not in the top-level namespace, like other struct { pub const X... members are:

pub const Interface = struct {
    pub const PI = 3.14;
    pub const parse: fn (str: []const u8) void = undefined;
};

pub fn parse(x: []const u8) void {}

test "redefined" {
    _ = Interface.PI;
    _ = Interface.parse;
}
./x.zig:3:9: error: redefinition of 'parse'
    pub const parse: fn (str: []const u8) void = undefined;
        ^
./x.zig:6:5: note: previous definition is here
pub fn parse(x: []const u8) void {}
    ^
./x.zig:10:18: note: referenced here
    _ = Interface.parse;
                 ^

Version 0.7.1 on Linux.

@g-w1
Copy link
Contributor

g-w1 commented Apr 14, 2021

If I did, const p = parse; inside the Interface struct, what would you expect to happen? Would it use the inner parse, or the outer one? How would you access the outer one from the inside?

@pfgithub
Copy link
Contributor

const sample = "one";
const duplicate = "one";
const Struct = struct {
    const duplicate = "two";
    pub fn something() void {
        _ = sample; // sample is "one". this is accessible even though it's in the outer scope because it's a comptime value
        _ = duplicate; // what is duplicate here? is it "one" or "two"? zig makes this a shadowing error instead
    }
};
test "" {_ = Struct.something;}

@mlawren
Copy link
Author

mlawren commented Apr 14, 2021

I don't want to use the outer one from inside, but I would like to use the inside from the outside, which I can do here:

const duplicate = "one";
const Struct = struct {
    duplicate: []const u8 = "two",
};
test "" {
    const x = Struct{ .duplicate = "three" };
    _ = duplicate;
    _ = x.duplicate;
}

If the above works for []const u8, why can't I do the same with a function?

const duplicate = "one";
const Struct = struct {
    duplicate: []const u8 = "two",
    parse: fn () void = undefined,
};
test "" {
    const x = Struct{
        .duplicate = "three",
        .parse = fn () void{},
    };
    _ = duplicate;
    _ = x.duplicate;
}
./k.zig:9:28: error: type 'fn() void' does not support array initialization
        .parse = fn () void{},
                           ^

This probably should have been my initial issue/query ... I guess I got sidetracked since it seems that the struct{ pub fn()... appeared to get me further along.

@mlawren
Copy link
Author

mlawren commented Apr 14, 2021

I see now that I raised / labelled this issue based on a misunderstanding - there is nothing specific to fn() types. This also fails to compile, because the pub const PI inside the struct (sort of) shares the same namespace as the file:

const expect = @import("std").testing.expect;
// Structs can have global declarations.
// Structs can have 0 fields.
const Empty = struct {
    pub const PI = 3.14;
};
pub const PI = 3.14;
test "struct namespaced variable" {
    expect(Empty.PI == 3.14);
    expect(@sizeOf(Empty) == 0);

    // you can still instantiate an empty struct
    const does_nothing = Empty{};
}
./l.zig:5:9: error: redefinition of 'PI'
    pub const PI = 3.14;
        ^
./l.zig:7:5: note: previous definition is here
pub const PI = 3.14;
    ^
./l.zig:9:17: note: referenced here
    expect(Empty.PI == 3.14);
                ^

What is the real namespace of Empty.PI? Zig allows a second struct in the same file with the same label:

const expect = @import("std").testing.expect;
pub const Empty = struct {
    pub const PI = 3.14;
};
pub const Empty2 = struct {
    pub const PI = 3.14;
};
test "struct namespaced variable" {
    expect(Empty.PI == 3.14);
    expect(Empty2.PI == 3.14);
}

What is the benefit of Zig having this File/Struct namespace constraint? Something in one namespace affecting a parent namespace fails to "Communicate intent precisely" to me. Here is another odd example:

const expect = @import("std").testing.expect;
pub const Empty = struct {
    pub const PI = 3.14;
};
pub const Empty2 = struct {
    child: Empty = Empty{},
    pub const PI = 3.14;
};
test "struct namespaced variable" {
    const e2 = Empty2{};
    expect(Empty.PI == 3.14);
    expect(e2.child.PI == 3.14);
    expect(Empty2.PI == 3.14);
}
./l.zig:12:20: error: no member named 'PI' in struct 'Empty'
    expect(e2.child.PI == 3.14);
                   ^

I think namespacing still needs a few deep thoughs.

On a design note, why is pub needed for struct constants when struct members don't need to be marked as public? For consistency I would have expected something like the following instead:

pub const Point3D = struct {
    x: 0,
    y: 0,
    const z: 0, // stay on the ground please
};

@mlawren mlawren changed the title Public struct member of type fn not namespaced? Namespace of pub const in structs is not obvious Apr 14, 2021
@pfgithub
Copy link
Contributor

pfgithub commented Apr 14, 2021

Shadowing errors in zig are about scope, not namespaces.

const Sample = struct {
    const PI = 3.14;
};

Here, Sample has a declaration PI of type comptime_float with the value 3.14

Inside the struct{} definition, you can refer to PI using its name directly. Outside of it, it is out of scope and you must use Sample.PI.

const Sample = struct {
    const PI = 3.14;
    // Sample is in scope, so you can use Sample.PI
    // PI is a variable that is in scope, so you can use PI directly
};
// Sample is in scope, so you can use Sample.PI
// PI is not in scope, you must use Sample.PI to access it

Note that pub is only needed to allow accessing a declaration from a different file.

What is the real namespace of Empty.PI? Zig allows a second struct in the same file with the same label

pub const Empty = struct {
    pub const PI = 3.14;
};
pub const Empty2 = struct {
    pub const PI = 3.14;
};

Empty.PI is a declaration in the struct Empty. Empty2.PI is a declaration in the struct Empty2

Here is another odd example

const expect = @import("std").testing.expect;
pub const Empty = struct {
   pub const PI = 3.14;
};
pub const Empty2 = struct {
   child: Empty = Empty{},
   pub const PI = 3.14;
};
test "struct namespaced variable" {
   const e2 = Empty2{};
   expect(Empty.PI == 3.14);
   expect(e2.child.PI == 3.14);
   expect(Empty2.PI == 3.14);
}

This produces the same error:

expect(e2.PI == 3.14);

You can only access struct declarations from the struct type, not from an instance of that struct

const Sample = struct {
    field: u8,
    const Decl = "hi";
};
test "" {
    _ = Sample.Decl; // OK
    _ = Sample.field; // Not OK
    const sample = Sample{.field = 10};
    _ = sample.field; // OK
    _ = sample.Decl // Not OK
    _ = @TypeOf(sample).Decl // OK
}

There is currently an exception here for function declarations (eg fn append(self: Sample) void {…}) to allow for thing.append() syntax sugar. This will have to be addressed as part of #1717.

On a design note, why is pub needed for struct constants when struct members don't need to be marked as public? For consistency I would have expected something like the following instead

The ability to have private fields was removed in #2059, I don't think a clear reason was given.

@mlawren
Copy link
Author

mlawren commented Apr 14, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants