Skip to content
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

Init std.io.BufferedWriter outside main function avoid flushing #20693

Closed
tiawl opened this issue Jul 20, 2024 · 1 comment
Closed

Init std.io.BufferedWriter outside main function avoid flushing #20693

tiawl opened this issue Jul 20, 2024 · 1 comment
Labels
bug Observed behavior contradicts documented or intended behavior

Comments

@tiawl
Copy link

tiawl commented Jul 20, 2024

Zig Version

0.13.0

Steps to Reproduce

Here a minimal exemple:

const std = @import ("std");

const EmptyMe = struct {
  const size = 4096;

  array: std.BoundedArray (u8, 2),
  buffered_stderr: std.io.BufferedWriter (size, std.fs.File.Writer) = undefined,
  stderr: std.io.BufferedWriter (size, std.fs.File.Writer).Writer = undefined,

  // Comment this and uncomment the matching function to make it works:
  fn init (writer: *const std.fs.File.Writer) !@This () {
    var self: @This () = .{
      .array = try std.BoundedArray (u8, 2).init (0),
      .buffered_stderr = std.io.bufferedWriter (writer.*),
    };
    self.stderr = self.buffered_stderr.writer ();
    return self;
  }

  // fn init () !@This () {
  //   return .{ .array = try std.BoundedArray (u8, 2).init (0), };
  // }

  fn empty (self: *@This ()) !void {
    defer {
      self.stderr.writeByte ('\n') catch {};
      self.buffered_stderr.flush () catch {};
    }
    while (self.array.len > 0) try self.stderr.writeByte (self.array.pop ());
  }
};

test "test" {
  const writer = std.io.getStdErr ().writer ();

  // Comment this and uncomment the comments below to make it works:
  var empty_me = try EmptyMe.init (&writer);

  // var empty_me = try EmptyMe.init ();
  // empty_me.buffered_stderr = std.io.bufferedWriter (writer);
  // empty_me.stderr = empty_me.buffered_stderr.writer ();

  try empty_me.array.append ('A');
  try empty_me.array.append ('B');
  try empty_me.empty ();
}

Observed Behavior

$ zig test test.zig
All 1 tests passed.

Expected Behavior

$ zig test test.zig
BA
All 1 tests passed.

Thank you for this awesome programming language.

@tiawl tiawl added the bug Observed behavior contradicts documented or intended behavior label Jul 20, 2024
@mlugg
Copy link
Member

mlugg commented Jul 20, 2024

Your assignment to self.stderr in init takes a pointer to self.buffered_stderr, which is leaked out of the function by return self. Since &self.buffered_stderr is a pointer to a local variable, that means the stderr field becomes invalid. However, it is later used, by empty. That means this code exhibits Unchecked Illegal Behavior.

You could solve this by having init take an out-pointer rather than returning a value. One day, this use case might also be solved by Result Location Semantics combined with Pinned Structs. More simply, you could just not store buffered_stderr.writer(), and construct it as a local in empty -- it's very cheap to construct.


In future, please zig fmt your code before posting an issue. The code you posted is quite difficult to read for some of us who are very familiar with canonical Zig style.

Also, a few code style points:

  • Defaulting those fields to undefined is a footgun. See the corresponding langref section for advice here.
  • @This() is unnecessary here; just write EmptyMe.
  • In init, that error should be handled with catch unreachable rather than try, because you know the error.Overflow case can't happen (since 0 is less than 2!).
  • empty uses defer incorrectly. defer exists primarily for "cleanup" operations, which need to be run even in error cases etc. The code in your defer there should only run in the non-error case; if writing to stderr has already failed, you don't want to try to write more and try to flush. Moreover, using a defer here forces you to use catch {}, which explicitly hides potential errors. Instead, you should just try these lines after the loop.

@mlugg mlugg closed this as not planned Won't fix, can't repro, duplicate, stale Jul 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Observed behavior contradicts documented or intended behavior
Projects
None yet
Development

No branches or pull requests

2 participants