External files

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: