-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
make usingnamespace
not put identifiers in scope
#9629
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
Comments
This proposed change also has the nice effect of removing an antipattern from the language: // The idiomatic way to import the standard library
const std = @import("std");
std.foo();
// A tempting pattern to avoid typing "std" everywhere
// This would no longer be possible with this proposal!
usingnamespace @import("std");
foo(); |
Currently I'm using this code:
To bring some constants into the scope (all constants are prefixed with DXGI_, for example After this change I will have to do something like this:
Am I correct? |
Yes you are correct. However if you control dxgi.zig and the other files then you could improve it by using real namespaces like this: const dxgi = @import("dxgi.zig");
const dxgi1_2 = @import("dxgi1_2.zig");
const dxgi1_3 = @import("dxgi1_3.zig");
_ = dxgi.CONSTANT_1;
_ = dxgi1_2.CONSTANT_2012;
_ = dxgi1_3.CONSTANT_323232; |
Note also that when interfacing with C code (which usually uses name prefixes as C lacks language-level namespacing), it is common to have a // in c.zig
pub includenamespace @cImport({
@cDefine("_POSIX_C_SOURCE", "200809L");
@cInclude("stdlib.h");
@cInclude("unistd.h");
});
pub const DXGI_CONSTANT_1 = 42;
pub const DXGI_CONSTANT_2 = 31415; // in main.zig
const c = @import("c.zig");
c.libfoo_do_bar(c.DXGI_CONSTANT_1); Using the single character prefix "c" keeps it clear where these declarations come from without being overly verbose. |
I will try to merge dxgi.zig, dxgi1_2.zig and dxgi1_3.zig into one dxgi.zig file. Then I will have:
Will see how it works. |
One issue I see with this is: In C, I can have one 'namespace' spread across several files. For example, DXGI_ 'namespace' is spread across files: This proposal forces Zig to have one namespace per file. I won't be able to have single huge namespace spread across several files. |
I would argue that if you want to break your namespace up into smaller logical chunks and split those between files, that logical division of the namespace should be expressed through sub-namespaces. However, what you want to do is still very much possible with the proposed changes: // in dxgi.zig
pub includenamespace @import("dxgi1_2.zig");
pub includenamespace @import("dxgi1_3.zig");
pub includenamespace @import("dxgi1_4.zig");
pub includenamespace @import("common.zig"); I would still recommend avoiding a single massive namespace though. Instead consider providing a bit more structure to your api through nested namespaces, similar to how the standard library has |
@ifreund Not sure I understand this. This won't work?
But below will?
|
@michal-z // old
const Foo = struct {
usingnamespace @import("std");
pub fn foo() void {
function_in_std();
}
}; // new
const Foo = struct {
includenamespace @import("std");
pub fn foo() void {
// Would be a compile error without the Foo.
Foo.function_in_std();
}
}; |
@ifreund Thanks. And how is As: I must say that this is a bit confusing for me. |
Keep in mind this proposal effectively only changes one thing: not putting identifiers in scope. Everything else works the same. I'm not sure how this question would come up, unless you also have the same question about status quo |
It would be visible as If As andrew points out, these semantics are exactly the same as those of the current using namespace. |
I see, all is clear now, thanks for explanation! |
@ifreund @andrewrk I know where my confusion came from, in status quo: const Foo = struct {
usingnamespace @import("std");
pub fn foo() void {
debug.print("aaa", .{}); // works
Foo.debug.print("aaa", .{}); // also works
}
}; With this proposal: const Foo = struct {
includenamespace @import("std");
pub fn foo() void {
debug.print("aaa", .{}); // does not work
Foo.debug.print("aaa", .{}); // works
}
}; The fact that both forms work in status quo was a bit confusing for me. |
While I think the proposed semantics are solid, I think the new keyword name could use some more consideration. The currently proposed I think using |
@ifreund I really like |
Would this still work with methods? const Foo = struct {
pub includenamespace struct {
pub fn bar(self: *Foo) { ... }
};
};
var foo = Foo { };
foo.bar(); Also would |
Yeah this is actually a pretty big deal. I recall working out proposals to enhance the D language in a way that would allow projects to use more explicit or namespaced imports that would allow those projects to get the same benefit. This enables the compiler to rule out having to analyze other modules, which was killing compilation performance in D. Here's that D proposal where I walk through what the problem is for those interested: https://github.com/marler8997/dlangfeatures#lazy-imports Here's a preview:
|
@marler8997 yes, both of those examples in your snippet would continue to work, just as they currently do with const Foo = struct {
pub includenamespace struct {
pub fn bar(self: *Foo) void { ... }
};
fn init(self: *Foo) void {
self.bar(); // still works
bar(self); // would no longer work
}
};
var foo = Foo { };
foo.bar(); // still works |
@ifreund I'm happy with the name
Over in self-hosted I started implementing this feature to see what it would look like and here is what I ended up doing on the first pass (slightly different semantics):
This is different than how stage1 does it, where it actually copies the names of decls from one table into another table, skipping the non-public declarations. With these different semantics in stage2, the compiler would only report an error if a name conflict was actually triggered via I do think how stage1 does it makes I think I'm leaning towards doing it the way I outlined here, the way the code kind of came out naturally in stage2. It requires storing fewer things in memory, and feels simpler in terms of dealing with incremental updates. However I'm wide open to feedback on this one. |
I had the exact same thought about that inconsistency while writing the many example snippets above. I think your proposed stage2 implementation/semantics would make the language more consistent, so +1 from me!
Cool, It's been growing on me over |
I suggest let's nail down the semantics, and make the keyword rename a separate proposal after that's done |
I've been working on an implementation and the new AstGen error finds a lot of broken code in std:
Seems pretty useful |
Just for information. I have restructured my code. I'm now using // win32.zig
pub const base = @import("windows.zig");
pub const dwrite = @import("dwrite.zig");
pub const dxgi = @import("dxgi.zig");
pub const d3d11 = @import("d3d11.zig");
pub const d3d12 = @import("d3d12.zig");
pub const d3d12d = @import("d3d12sdklayers.zig");
pub const d3d = @import("d3dcommon.zig");
pub const d2d1 = @import("d2d1.zig");
pub const d3d11on12 = @import("d3d11on12.zig");
pub const wic = @import("wincodec.zig");
pub const wasapi = @import("wasapi.zig");
One more place where I use // windows.zig
pub usingnamespace @import("std").os.windows;
pub usingnamespace @import("misc.zig"); Sample application uses it like this: const win32 = @import("win32");
const w = win32.base;
const d2d1 = win32.d2d1;
const d3d12 = win32.d3d12;
const dwrite = win32.dwrite;
//...
demo.brush.SetColor(&d2d1.COLOR_F{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 });
//...
//...
const cmdqueue = blk: {
var cmdqueue: *d3d12.ICommandQueue = undefined;
hrPanicOnFail(device.CreateCommandQueue(&.{
.Type = .DIRECT,
.Priority = @enumToInt(d3d12.COMMAND_QUEUE_PRIORITY.NORMAL),
.Flags = d3d12.COMMAND_QUEUE_FLAG_NONE,
.NodeMask = 0,
}, &d3d12.IID_ICommandQueue, @ptrCast(*?*c_void, &cmdqueue)));
break :blk cmdqueue;
};
//... |
After exploring what it would look like to implement usingnamespace with these semantics in self-hosted, as well as exploring what std lib changes would need to be made to adjust to these semantics, I'm confidently marking this as accepted. @SpexGuy pointed out to me that this proposal also solves a design flaw that I ran into in self-hosted, because it allows AstGen to make note of all the ZIR instructions that are referenced inside any given Decl. This is a solution to a fundamental problem, unblocking progress on the self-hosted compiler. |
See #9618 for an ongoing effort to implement this. |
The proposal #9629 is now accepted, usingnamespace stays but no longer puts identifiers in scope.
When you are talking about mixins, it's almost always accompanied with a custom merging strategy—and a default one. |
The proposal #9629 is now accepted, usingnamespace stays but no longer puts identifiers in scope.
The proposal #9629 is now accepted, usingnamespace stays but no longer puts identifiers in scope.
The proposal #9629 is now accepted, usingnamespace stays but no longer puts identifiers in scope.
The proposal #9629 is now accepted, usingnamespace stays but no longer puts identifiers in scope.
The proposal #9629 is now accepted, usingnamespace stays but no longer puts identifiers in scope.
Implemented in 594271f. The keyword rename can be a separate proposal. |
Are all |
pub const sub_namespace = struct {
includenamespace @import("file1.zig");
pub includenamespace @import("file2.zig");
};
comptime {
_ = sub_namespace.private_decl;
}
|
From status quo the proposed change is very small:
usingnamespace
toincludenamespace
.Example:
Why? What material benefit does this have? Turns out, it's actually a big deal for the design of an incremental compiler:
AstGen is the place where we report shadowing of identifiers and unused identifiers. It would make sense to also report error for use of undeclared identifier. However the existence of
usingnamespace
makes that not an option. Having this error in AstGen means that the compile error is available non-lazily. That is, you will see errors of this form even for code that does not get semantically analyzed. For example, on Windows you would still see "use of undeclared identifier" errors when building for macOS.With status quo semantics, an identifier in scope of more than one
usingnamespace
forces all of them to be resolved, in order to make sure there are no name conflicts. However, usinga.b
syntax means only theincludenamespace
declarations that apply toa
must be resolved. With incremental compilation, having an identifier force an unrelatedusingnamespace
to be resolved creates a dependency between these two things. This dependency costs perf, memory, and decreases the amount of encapsulation of changes - that is, it makes an incremental update less incremental. Theincludenamespace
semantics reduce the amount of such dependencies.The text was updated successfully, but these errors were encountered: