#return string from user input

1 messages · Page 1 of 1 (latest)

peak bane
#

Hello! I am trying to write a function that would take a user input and return it as a string.
I have made this function so far:

fn ask_user(comptime instruction: []const u8) ![]const u8 {
    const stdin = std.io.getStdIn().reader();
    const stdout = std.io.getStdOut().writer();

    if (!std.mem.eql(u8, instruction, "")) {
        try stdout.print(instruction, .{});
    }
    const bare_line = try stdin.readUntilDelimiterAlloc(
        std.heap.page_allocator,
        '\n',
        10,
    );
    defer std.heap.page_allocator.free(bare_line);

    const line = std.mem.trim(u8, bare_line, "\r");
    print("in ask user: |{s}|\n", .{line});
    return line;
}

pub fn main() !void {
    var user_input = try ask_user("your turn: ");
    print("user input: |{s}|\n", .{user_input});
}

The print statement in ask_user displays the expected string. There is no compilation error. However, during execution, the main function panics: panic: reached unreachable code. I am assuming it's because the line variable is "de-allocated" after the function returns. However, after reading this article (https://zig.news/kristoff/what-s-a-string-literal-in-zig-31e9), I was expecting the code to be working.

I don't know what I am missing, could someone help me?

frank canyon
#

the article is talking about string literals, i.e. things like ”hello”. user input is just strings, and they arent stored in the data section because they cant be. only things known at comptime are

#

here you're freeing the memory (defer std.heap.page_allocator.free(bare_line);) before the line in the main function can use it (print("user input: |{s}|\n", .{user_input});)

#

also, you shouldnt be using page_allocator unless youre writing an allocator interface. it uses syscalls (which are very slow) and returns whole pages for each allocation (4096 bytes on windows) which is very wasteful. you should instead accept an std.mem.Allocator parameter for any function that needs to allocate and use that. std.heap.GeneralPurposeAllocator is a good catch-all allocator

#

to properly manage the memory, you should dupe line (Allocator.dupe) before you return it and use defer alloc.free(user_input) after var user_input = ...

somber nimbus
# peak bane Hello! I am trying to write a function that would take a user input and return i...

this is how i would write that:

const std = @import("std");
const print = std.debug.print;

fn askUser(allocator: std.mem.Allocator, maybe_instruction: ?[]const u8) ![]const u8 {
    const stdin = std.io.getStdIn().reader();
    const stdout = std.io.getStdOut().writer();

    if (maybe_instruction) |instruction| {
        try stdout.writeAll(instruction);
    }
    const bare_line = try stdin.readUntilDelimiterAlloc(
        allocator,
        '\n',
        10,
    );
    defer allocator.free(bare_line);

    const line = std.mem.trim(u8, bare_line, "\r");

    return allocator.dupe(u8, line);
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.assert(gpa.deinit() == .ok);
    const allocator = gpa.allocator();

    var user_input = try askUser(allocator, "your turn: ");
    defer allocator.free(user_input);

    print("user input: |{s}|\n", .{user_input});
}

changes:

  • instruction is no longer comptime, stdout.writeAll is used instead of print since there's no formatting being done (print("{s}", .{instruction}); would have also allowed it to be non-comptime)
  • instruction is nullable instead of checking for ""
  • askUser takes an allocator as a parameter (this is the zig way to handle allocation/allocators)
  • askUser dupes the trimmed line and returns it
  • a GeneralPurposeAllocator (which will check for memory leaks, double frees, etc for you) is instantiated in main and that's what's passed to askUser
  • user_input returned from askUser is freed by a defer in main
merry grotto
#
fn askUser(maybe_instruction: ?[]const u8) !std.BoundedArray(u8, 10) {
    var result = std.BoundedArray(u8, 10){};
    const stdin = std.io.getStdIn().reader();
    const stdout = std.io.getStdOut().writer();

    if (maybe_instruction) |instruction| {
        try stdout.writeAll(instruction);
    }
    const bare_line = try stdin.readUntilDelimiter(
        result.unusedCapacitySlice(),
        '\n',
    );

    result.resize(bare_line.len - if(bare_line[bare_line.len - 1] == '\r') 1 else 0)

    return result;
}

no allocation needed

somber nimbus
rich moth
#

what's the meaning of the {} at the end of
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
and
var result = std.BoundedArray(u8, 10){}; ?

long sail
#

both std.heap.GeneralPurposeAllocator and std.BoundedArray are functions which return types. and the {} instantiates those types.

#

same as

const S = struct{a: u8 = 0};
var s = S{};
#

since all of S's fields have default values, it can be init w/ no fields

#

same is true for GPA and BoundedArray

rich moth
#

perfect thank you!

peak bane
#

@frank canyon thank you for the advice on allocators. It's still very new to me, it's nice to have some guidelines.

peak bane
# somber nimbus this is how i would write that: ```rust const std = @import("std"); const print...

Thank you so much for the detailed answer! Making instruction nullable is very nice.

So, if I understand correctly, an allocator is something that will allocate/free memory on demand. So in the main function for example, allocator.free(user_input) means take the memory block starting at user_input and free it?

Another question: in the source code, the readUntilDelimiter function is deprecated, (0.12-dev), I guess I should use something else?

After reviewing the other answer, there is a way to do the same thing without using an allocator. From my point of view, it seems simpler, hence better. Can you think of a situation where using an allocator is more accurate?

peak bane