I like that the compiler can distinguish those two things too, so that I can refer to things within modules even if I happen to also have a local variable with the same name. Go sometimes annoys me because I need to rename a module or a variable just because the syntax doesn't distinguish between the two things.
zig: what's the difference?
In C3 it goes the other way despite having methods, and says "let's not allow static variables or methods".
This also goes hand in hand in the language approaches between open/close. Zig is very strongly "closed" and C3 is very much "open" (adding methods to types anywhere, appending to modules anywhere etc)
It's an interesting contrast that leads to very different code layouts.
unions and enums also create namespaces. any @import creates a namespace that is a struct.
If a function's first argument is the type of the function's namespace or a pointer to the type of the namespace, you may (and by convention are encouraged to) use dot dereferencing (like python firso parameter self) as a lexical sugar to call the function with the "implicitly rearranged" first parameter ("oop style caling, but not really oop")
modules are somewhat different, in zig these are collections of code you can map to a non-filepath import string using the command line or build tool (in particular you may map something out of code root path), but these too ultimately become a namespace struct
modules (might not mean the same as module in any other given lang) ⊂ imports ⊂ structs ⊂ namespaces ⊂ types
MyModule.zig
somefield: u32,
fn get(self: @This()) u32 {
return this.somefield;
}
main.zig const Module = @import("MyModule"); // note there are build parameters necessary to make this a module
fn main() void {
var x: Module = .{.somefield = 42 };
_ = x.get() // => 42
...
A module is an import external to your project. Whether or not the import has top level fields or declarations, the mechanism is the same for structs, imports, and modules. For all imports (including modules) you should imagine an implicit `struct{...}` expression surrounding the code in your file.Do you mean object like object file (to be linked)? Those don't have member functions as far as I know.
I'm using "object" as it is commonly used; essentially a value. In e.g Java or C or C++, the struct/class is a type, and an instance of the struct/class is an object.
Accessing an object's members is a different operation from looking up a symbol in a namespace. Even in Zig.
In C3 there is something called "path shortening", allowing you to use `foo::bar()` in place of something like `std::baz::foo::bar()`. To do something similar with `.` is problematic, because you don't know where the path ends. Is `foo.baz.bar()` referring to `foo::baz::bar()` or `foo::baz.bar()` or `foo.baz.bar()`?
It feels more lightweight and consistent, and collisions aren’t super common once you adopt some conventions.
It’s a tradeoff for sure, but this preference comes from having lived in both worlds.
Yeah, but in practice I try not to produce write-only code.
Code is for reading more than for writing, hence make it easier to read. If it becomes easier to write as a side-effect, then so be it.
The identifier on the right is looked up in the scope of the identifier on the left. If it resolves to a module, then it's a module. If it resolves to a function, then it's a function. If the left side is a pointer (not a symbol with a scope) then the right side resolves to a member.
It also makes refactoring much easier - changing a pointer to a reference does not require a global search/replace of -> with .
Also, "io", "file", "random" etc are commonly used variables, so the issue with shadowing is real.
`File file = file::open(...)` is completely unambiguous and fine. `File file = file.open(...)` on the other hand would be bad.
If the language had flat modules, or no path shortening, then it would be possible.
D uses a spell checker for undefined identifiers, and the dictionary is all the identifiers in scope. It has about a 50% success rate in guessing which identifier was meant, which is quite good.
> Also, "io", "file", "random" etc are commonly used variables, so the issue with shadowing is real.
If the same identifier is accessible through multiple lookup paths, an error is issued. If a local variable shadows a variable in an outer scope, and error is issued.
We've developed this over several years, and it works quite well.
Path shortening can be done with:
alias open = file.open;
or: import io: open;In Rust where modules share a namespace with other identifiers, I just pick different variable names, or write my imports so they don't conflict. It's not that big a deal.
When Rust was in its infancy, I maintained a local fork for a while where I modified the parser to use the Ada syntax («'», a single apostrophe) for the same purpose. So
std::collections::hash_map::HashMap::entry
became std'collections'hash_map'HashMap'entry
But I am not convinced that using «.» for two distinct purposes – 1) as a name qualifier, and 2) as a method/data element accessor – is a good idea, for maintaining the semantic clarity is important, i.e. is «e» in «a.b.c.d.e» 1) A method call; or
2) An accessor for a deeply nested data structure.
It is not easy to say whilst just glancing upon the code.