## Motivation
Files are implicitly structs, which lets you do things like:
…
```zig
//! Allocator.zig
ptr: *anyopaque,
vtable: *const VTable,
pub const VTable = ...
```
```zig
//! main.zig
const Allocator = @import("Allocator.zig");
const allo = Allocator {
.ptr = *myAllocFn,
.vtable = ...
};
...
```
which implicitly expands into
```zig
//! main.zig
const Allocator = struct {
ptr: *anyopaque,
vtable: *const VTable,
pub const VTable = ...
};
const allo = Allocator {
.ptr = *myAllocFn,
.vtable = ...
};
...
```
This convenience technique is [widely used in the stdlib](https://github.com/ziglang/zig/issues/6617#issuecomment-705869521) for concrete types.
However, because `@import` essentially wraps an implicit `struct{ ... }` around the file content, it is not possible to declare packed structs or enums this way.
For example, imagine a cumbersome file that declares a u8 enum of keycodes, this is how it must be done at the moment:
```zig
//! keycodes.zig
pub const Keycode = enum(u8) {
...
Key8 = '8',
Key9 = '9',
A = 'A',
B = 'B',
...
Num1 = 0x61,
Num2 = 0x62,
...
unmapped = 0xFF,
};
// nothing else in file-scope
```
its usage entails `const Keycode = @import("keycodes.zig").Keycode;`
rather than just `const Keycode = @import("keycodes.zig");`
even though the file defines nothing else.
## Proposal
What I would like to propose is to
(1) make `@import` scopes implicitly an _expression_ rather than being the _body_ of an implied `struct{ ... }`, and
(2) make status-quo bare declaration files implicitly wrapped inside a **_non-instantiable_** namespace construct.
To illustrate, status quo namespace files will still look exactly the same:
```zig
///! std.zig
pub const ArrayHashMap = array_hash_map.ArrayHashMap;
pub const ArrayHashMapUnmanaged = array_hash_map.ArrayHashMapUnmanaged;
...
```
```zig
//! main.zig
const std = @import("std");
pub fn main() void {
std.debug.print("hello world", .{});
}
```
which implicitly expands into
```zig
//! main.zig
const std = opaque {
pub const ArrayHashMap = array_hash_map.ArrayHashMap;
pub const ArrayHashMapUnmanaged = array_hash_map.ArrayHashMapUnmanaged;
...
};
pub fn main() void {
std.debug.print("hello world", .{});
}
```
But file-scope Types must now be prefixed with a type definition keyword, i.e.
```zig
//! Allocator.zig
struct {
// helper imports and decls
...
// The type erased pointer to the allocator implementation
ptr: *anyopaque,
vtable: *const VTable,
pub const VTable = ...
}
// no semicolon, since this is an expression
// nothing else can be defined in file-scope
```
While this creates the disadvantage for the _code writer_ of needing to indent everything by one extra level, in return we receive the advantage that the _code reader_ can more readily expect to find field definitions somewhere in the middle of the file, aside from just filename capitalization which may or may not be lying.
It also lets us more cleanly quarantine unwieldy `extern struct` definitions into individual files:
```zig
//! NOTIFYICONDATAA.zig
extern struct {
// helper imports and decls
const std = @import("std");
const win = std.os.windows;
// the actual struct definition
cbSize: win.DWORD,
hWnd: win.HWND,
uID: win.UINT,
uFlags: win.UINT,
uCallbackMessage: win.UINT,
hIcon: win.HICON,
szTip: if(isLaterThanWin2k()) [127:0]win.CHAR else [63:0]win.CHAR,
dwState: win.DWORD,
dwStateMask: win.DWORD,
szInfo: [255:0]win.CHAR,
DUMMYUNIONNAME: extern union {
uTimeout: win.UINT,
uVersion: win.UINT,
},
szInfoTitle: [63:0]win.CHAR,
dwInfoFlags: win.DWORD,
guiItem: win.GUID,
hBalloonIcon: win.HICON,
// a bunch of ad-hoc helper functions for parsing/constructing this godforsaken abomination
...
}
// no semicolon, since this is an expression
// nothing else can be defined in file-scope
```
Additionally, with the benefit of generally being an _expression_, one can achieve comptime conditional declarations by making the file-scope an execution block, which supercedes certain usecases of `usingnamespace`:
```zig
//! MyThing.zig
comptime do: {
const MyThing_variant_A = struct {
data1: u32, data2: u32,
const operate = @import("variant_A").operate;
};
const MyThing_variant_B = struct {
data1: u32, data2: u32, data3: u32,
const operate = @import("variant_B").operate;
};
break :do if(compile_variant_A()) MyThing_variant_A else MyThing_variant_B;
}
```
Or directly return generic type functions:
```zig
//! MyGeneric.zig
(opaque {
fn MyGeneric(comptime T: type) type {
return struct {
data1: T, data2: T, data3: T,
pub fn ...
};
}
}).MyGeneric
```
```zig
//! main.zig
const Generic_f32 = @import("MyGeneric.zig")(f32);
const Generic_f64 = @import("MyGeneric.zig")(f32);
pub fn main() void {
...
}
```
Furthermore, it also allows us to directly import ZON files as valid zig code, since ZON itself is an expression as well.
This is somewhat comparable to Lua's `require` syntax being implicitly function returns, which lets you procedurally construct what you want to return:
```lua
-- Thing.lua
local ret = {}
local function doMyThing_general(arg)
...
end
local function doMyThing_win32(arg)
...
end
local function doMyThing_linux(arg)
...
end
do
ret.generalThing = doMyThing_general
if isWin32() then
ret.specificThing = doMyThing_win32
ret.os = win32
elseif isLinux() then
ret.specificThing = doMything_linux
ret.os = linux
else
ret.specificThing = ret.generalThing
end
end
return ret
```
```lua
-- main.lua
local Thing = require('Thing')
local current_os = Thing.os
local specificResult_of_3 = Thing.specificThing(3)
...
```