External files
Reading external files
Reading external files is a common task in programming. In this section we are going to look at a few different ways to achieve it.
@embedFile
This method is simply going to embed the file in the binary at compile time. So if your file is large it might not be the best option, but for small files you are going to get the advantage of not having to open a file at runtime.
Note that their are plans to add this feature to C as well.
Beware that by using this method the path to the file is going to be relative to the file you are calling it from and that the file must be in the same working directory. If the file that is needed is somewhere else, it is mandatory to use other methods like the ones below for exemple.
const input = @embedFile("input.txt");
std.debug.print("{s}", .{input});
Use an allocator to dynamically store the content of the file
A bit closer to what you except to see in C there is a way of opening files using the readToEndAlloc method from the std.fs.File. This is useful the size of the file that is going to be opened is unknown or big. By doing this you are going to leverage heap memory allocation to store exactly what you need.
// Alocator
var gp = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
defer gp.deinit();
const allocator = gp.allocator();
// Path
var path_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const path = try std.fs.realpath("./input.txt", &path_buffer);
// Open file
const file = try std.fs.openFileAbsolute(path, .{});
defer file.close();
// Read
const file_content = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
defer allocator.free(file_content);
Read the file and put it in a buffer
An alternative, which is the classic C way of doing this, is using the method readAll, this is the most traditional way to do it. Like in in C the content of a file is going to be stored in a buffer allocated in the stack. Useful particulary if you know the size of the file you want to read.
// Path
var path_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const path = try std.fs.realpath("./input.txt", &path_buffer);
// Open file
const file = try std.fs.openFileAbsolute(path, .{});
defer file.close();
// Read
var content_buffer: [1024]u8 = undefined;
const size = try file.readAll(&content_buffer);
std.debug.print("{s}", .{content_buffer[0..size]});
If you want to partially read you can set a smaller buffer size and use the read method.
mmap from the C library
If ou want to have shared memory between processes or optimize your code you can use the mmap function from the C library.
// Path
var path_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const path = try std.fs.realpath("./input.txt", &path_buffer);
// Open file
const file = try std.fs.openFileAbsolute(path, .{});
defer file.close();
// Get the file size
const file_info = try file.stat();
const filesize = file_info.size;
// Map the file into memory
const mapped = try std.os.mmap(null, filesize, std.os.linux.PROT.READ, std.c.MAP.PRIVATE, file.handle, 0);
defer _ = std.c.munmap(mapped.ptr, filesize);
// Read and write to the stdout DIRECTLY
const stdout = std.io.getStdOut().writer();
try stdout.print("{s}", .{mapped});
Note that for this method to work, linking libc is mandatory.
Sources: