Type mismatch when importing vs when returned from function

I am running into something weird where I don’t know wether it’s a bug or an issue/misunderstanding on my end. I am creating a window for different platforms and return the correct window depending on platform as follows:

src/platform/win32/window.zig

const Self = @This();

pub fn init() !Self{
//...windows specific code
return Self{};
}

src/platform/x11/window.zig

const Self = @This();

pub fn init() !Self{
//...windows specific code
return Self{};
}

src/platform/window.zig

const builtin = @import("builtin");

pub usingnamespace switch (builtin.os.tag) {
    .windows => @import("win32/Window.zig"),
    .linux => @import("x11/Window.zig"),
    else => error.PlatformNotSupported,
};

src/root.zig

pub const Window = @import("platform/Window.zig");

Than if I use this in my main.zig as follows:

// engine is the name of the module containing root.zig
const Window = @import("engine").Window;

pub fn main() !void {
   var window: Window = undefined;
   window = try Window.init();
}

I get the error that the variable window is of type platform.Window
but the return value of Window.init() is of type platform.win32.Window.

Should those not be the same type? as I can call Window.init() on it.

usingnamespace will only create references to declarations. It will not copy them over and it will not re-evaluate them. This can be seen in the following example:

const std = @import("std");

const A = struct {
	var value: u32 = 0;
};

const B = struct {
	usingnamespace A;
};

pub fn main() !void {
	A.value += 1;
	B.value += 15;
	std.log.err("{} {}", .{A.value, B.value}); // Prints 16 16
}

That’s why in your case platform.Window.Self is platform.win32.Window.
And additionally usingnamespace will not copy fields. So platform.Window is just an empty struct (which I guess is not what you want either).

In the standard library this problem is often solved by having wrapper functions(take a look at std.Thread for example):

// platform/window.zig
const Impl = switch (builtin.os.tag) {
    .windows => @import("win32/Window.zig"),
    .linux => @import("x11/Window.zig"),
    else => error.PlatformNotSupported,
};

impl: Impl,

pub fn init() !Self {
    return .{.impl = try Impl.init()};
}
... // Wrappers for all the functions you need
1 Like

Thanks, I solved it a little bit differently but you set me on the correct path.

I changed src/platform/Window.zig to

const builtin = @import("builtin");

pub const Window = switch (builtin.os.tag) {
    .windows => @import("win32/Window.zig"),
    .linux => @import("linux/window.zig"),
    else => error.PlatformNotSupported,
};

and then in root.zig

pub const Window = @import("platform/Window.zig").Window;

This prevents me from needing to wrap anything.

3 Likes