Zig: remove the (namespace) type and make every file an empty struct

Created on 4 Jun 2018  路  18Comments  路  Source: ziglang/zig

There's not much of a difference between @typeOf(@import("std")) and struct {}. Here are the current differences for example:

  • you cannot instantiate a namespace
  • namespaces cannot have fields
  • reflection reveals the top level declarations of structs but not namespaces (issue #1043)
  • structs cannot have tests inside them (but why not?)
  • structs cannot have use inside them (but why not?)

Making a file be an empty struct instead of a namespace would accomplish the following things:

  • solve the "but why not?" questions above

    • this is especially attractive for the reflection use case since one could eliminate redundant code for namespaces and structs

  • remove a type from the language, thus making the language simpler and smaller
  • if we allowed fields at top level, then it would solve the redundant import problem:

https://github.com/ziglang/zig/blob/96164ce61377b36bcaf0c4087ca9b1ab822b9457/std/index.zig#L3-L10

Thanks @tgschultz for coming up with this idea.

accepted proposal

Most helpful comment

@Hejsil I would argue no, unless the type had 1 or more fields.

All 18 comments

This is interesting

foo.zig
```C
var x: i32 = 1;
````

When you var foo = @import("foo.zig"); in multiple places does each foo get is own copy of x? Also what about if you import in a threads function, is that now a thread local instance of foo?

@bheads There would only exist one x in your case, as var x: i32 = 1 is a definition and not a field.
This proposal would basically allow us to do this:

// Point.zig
const zero = this{ .x = 0, .y = 0 };

x: u64,
y: u64,
// test.zig
const Point = @import("Point.zig");

test "points" {
    const p1 = Point{ .x = 0, .y = 2 };
    const p2 = @import("Point.zig"){ .x = 0, .y = 2 };
    const zero1 = Point.zero;
    const zero2 = @import("Point.zig").zero;
}

The file will be a struct definition just like if you did const Point = struct { ... };

@Hejsil Right (my usage was wrong) wouldn't we lose global variables? Not the worse thing in the world but can be necessary.

@bheads Aah, that's what you were after. No, as structs can have global variables in their "namespace":

const A = struct {
    var a: u8 = 2;
};

test "" {
    A.a = 15;
}

Though I guess you would more refer to those variables as 'static' usage wise the same.

If the type of namespace changes to type, should we follow the naming convention of types?

const Std = @import("std");

@Hejsil I would argue no, unless the type had 1 or more fields.

  • structs cannot have use inside them (but why not?)

At some point I was going to propose use inside a struct as a mechanism for making subtypes. At the time it felt like too much of an overload of use and an inferior solution to embedding the struct in its own field

const A = struct {
    var g = 4;
    a: u8,
    pub fn foo() { }
}

const B = struct {
    use A;
    b: u8 
}

// I'm not sure I'd make this fail only because of the redefinition of a, or because of the differing types
const Fails = struct {
    use B;
    a: f32
}

Where B is equivalent to

const B = struct {
    var g = 4;
    a: u8,
    b: u8
    pub fn foo() { }
}
  • structs cannot have tests inside them (but why not?)

Yeah, why not!

@raulgrell that sounds a lot like what D did with alias this, useful for making wrapped types but leads to complexity (ie can you support multiple use?)

This reminds me of ML modules :+1:

but also of Java mandatory classes everywhere 馃憥

@andrewrk, regarding the lambda issue, is there a way that a struct type could be derived from a local scope's context (by some comptime function?), in order to capture some names into the new struct type? I'm imagining something a little bit like inheritance except applied to a particular scope, maybe including the definition of new methods....

Another question is defining an anonymous method on a struct to allow it to be evaluated as a "function" / functor.

@bheads: Yeah, it can get hairy. I guess multiple use would have the same restrictions and layered use... fine as long as names don't collide?

@binary132: If the top level of java files were implicitly considered a class, it wouldn't be too bad. Processing is basically a preprocessor on top of java that just concatenates all your your source files into a single class and adds a bit of boilerplate. Pretty seamless if you're being generous =)

In any case, I'm not really pleased with my example...

Being able to pass "namespaces" as comptime parameters to functions would be great when mocking for unit tests.

I actually thought files were structs before I read this.

1685 implements the syntax changes, but things like tests in structs don't work yet.

hi everybody. thank you for zig, this is my first comment here and I'm still learning zig, sorry if this sounds silly.
wouldn't be nice if we remove @import and just expose all files in source directory globally, this way is like that you have a single file. like this:

// main.zig
const subns1 = ns1.subns1;
pub fn main() void {
    subns1.hello();
}
// ns1/subns1.zig
pub fn hello() void {
}

it maybe better to force .zig extension to directory name or ignore ns1.zig file to avoid future name collision.

@roofha, interesting thought but I think being explicit is better. There are times you have arbitrary files in a directory (a snippet or experiment) and you wouldn't want those automatically "imported".

@winksaville I didn't mean to import them all. I haven't thought about the implementation but I'm guessing the compiler can look for a definition of a symbol in another file with the same name when it doesn't find it in the current file, if this is bad for performance reasons, we can prepend echo external symbol like this: global.namesapce to make it explicit so the compiler know when to import another file

Was this page helpful?
0 / 5 - 0 ratings