joed
September 1, 2025, 10:15am
1
std.Thread.Pool
is defined with the following fields:
mutex: std.Thread.Mutex = .{},
cond: std.Thread.Condition = .{},
run_queue: RunQueue = .{},
is_running: bool = true,
allocator: std.mem.Allocator,
threads: []std.Thread,
The first four are provided with defaults, and the last two are provided in the Options struct when calling .init
:
pub const Options = struct {
allocator: std.mem.Allocator,
n_jobs: ?usize = null,
track_ids: bool = false,
stack_size: usize = std.Thread.SpawnConfig.default_stack_size,
};
pub fn init(pool: *Pool, options: Options) !void {
In tests at the bottom of the file, it appears that std.Thread.Pool
is intended to be initialized as follows:
var pool: Pool = undefined;
try pool.init(.{
.allocator = std.testing.allocator,
});
It’s my understanding of the language semantics that undefined
means that the variable is not in a well-defined state, so it shouldn’t be assumed that the fields were initialized to their default values. Is that not correct? If so, then isn’t this initialization pattern illegal behaviour?
1 Like
joed
September 1, 2025, 10:27am
2
Wait. I see that in the init function those fields are being assigned implicitly on the pool.* = .{
line.
My mistake. I need more coffee
5 Likes
Because the current zig cannot reference the result position before return
, this compromise is made.
I believe that if this issue is resolved, the std.Thread.Pool
initialization API could be designed to directly return to the result location.
opened 03:09AM - 27 Jun 19 UTC
proposal
This issue is split from #287.
Now that we have result location semantics, th… e following code does not introduce an intermediate value with a copy:
```zig
const Point = struct {
x: i32,
y: i32,
};
fn foo() Point {
return bar();
}
fn bar() Point {
return Point{
.x = 1,
.y = 2,
};
}
test "result location" {
var point = foo();
}
```
Previously, the code `return bar()` would introduce an extra copy, so the body of the function `foo` would needlessly copy the point before returning it. This copying would happen at *every expression, recursively* when the type is an aggregate type (such as `struct`). Now that the result location mechanism is merged into master, you can see that the `foo` function does not introduce an extra copy:
```llvm
define internal fastcc void @foo(%Point* nonnull sret) unnamed_addr #2 !dbg !35 {
Entry:
call fastcc void @bar(%Point* sret %0), !dbg !44
ret void, !dbg !46
}
```
However, if you capture the result in a variable and then return the variable, there is an intermediate value - the `result` variable - which is copied at the `return` statement:
```zig
fn foo() Point {
const result = bar();
return result;
}
```
Now there is a copy, because the Result Location of `bar()` is the `result` local variable, rather than the return result location:
```llvm
define internal fastcc void @foo(%Point* nonnull sret) unnamed_addr #2 !dbg !35 {
Entry:
%result = alloca %Point, align 4
call fastcc void @bar(%Point* sret %result), !dbg !47
call void @llvm.dbg.declare(metadata %Point* %result, metadata !45, metadata !DIExpression()), !dbg !48
%1 = bitcast %Point* %result to i8*, !dbg !49
%2 = bitcast %Point* %0 to i8*, !dbg !49
call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %2, i8* align 4 %1, i64 8, i1 false), !dbg !49
ret void, !dbg !50
}
```
This issue is to make it so that there is a way to refer to the result location, and even call methods on it, before returning it, all without introducing an intermediate value.
For the issue about getting rid of intermediate values when optionals and error unions are involved, see #2761.
this is still a good example to show why using default values can be trap. The code here was written well, but if it hadn’t been, and it’s easy to see how it could have gone wrong, then I think your reasoning is correct.
1 Like
joed
September 1, 2025, 12:58pm
5
I don’t really use them much myself for reasons like this.
The main exception being config/option struct parameters; there I think they make the code a lot more readable and ergonomic to write.
2 Likes