You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When attempting to open a file using std::fs::OpenOptions with both .read(true) and .create(true) set, but without.write(true) or .append(true), the open() method returns an io::Error with kind: InvalidInput (corresponding to EINVAL / OS error 22 on Linux).
My intention was to create a marker file that indicates the process started, without writing anything into it.
use std::fs::OpenOptions;use std::io;use std::path::Path;fnmain() -> io::Result<()>{let path = Path::new("/tmp/rust_test_marker_file");println!("Attempting: OpenOptions::new().read(true).create(true).open(...)");// This combination failslet file = OpenOptions::new().create(true)// Create if not exists// .write(true) // NOTE: Adding this makes it work.open(path);match file {Ok(f) => {println!("Success! Opened file: {:?}", f);Ok(())}Err(e) => {eprintln!("----------------------------------------");eprintln!("Failed to open file!");eprintln!("Error: {:?}", e);eprintln!("Kind: {:?}", e.kind());eprintln!("OS Error: {:?}", e.raw_os_error());eprintln!("----------------------------------------");Err(e)}}}
Running the above code produces Error: Os { code: 22, kind: InvalidInput, message: "Invalid argument" }.
Because this snippet was part of a larger system, I spent considerable time debugging my logic and, eventually, using strace to determine what was wrong. strace confirmed that the error occurs before any open or openat system call is attempted for the target file, indicating that the validation failure happens within the Rust standard library.
Analysis of the std::fs::sys::unix::fs source reveals a check in OpenOptions::get_creation_mode that explicitly returns EINVAL if create, create_new, or truncate is set withoutwrite or append also being set.
Crucially, the underlying Linux syscall does permit this combination, as the following C code demonstrates:
It successfully executes open(path, O_RDONLY | O_CREAT, 0644).
The current documentation for OpenOptions::open lists potential errors, including:
InvalidInput: Invalid combinations of open options (truncate without write access, no access mode set, etc.).
While the failing case is considered an invalid combination by the Rust standard library, it is not explicitly listed. The phrase "etc." does not make it clear that requesting creation inherently requires requesting write or append access within the OpenOptions builder—even though the underlying OS (like Linux) doesn't enforce this restriction for O_RDONLY | O_CREAT.
This can be surprising for users expecting behavior aligned with OS syscalls.
Note
The .create() method’s documentation does specify that .write(true) or .append(true) must also be set for .create(true) to actually create a file. However, this restriction is not repeated in the .open() documentation, nor is it reflected in error messages. This makes it easy to overlook, leading to confusion and unnecessary debugging.
Suggestion
Please consider updating the documentation for OpenOptions::open and/or the io::ErrorKind::InvalidInput description to explicitly state that setting .create(true), .create_new(true), or .truncate(true) also requires .write(true) or .append(true) to be set. This would make the behavior much less surprising for users expecting alignment with the underlying OS.
Alternatively, is this validation check actually necessary? If not, perhaps it could be relaxed to match OS behavior. At minimum, returning a more specific error message (rather than "Invalid argument") would also help prevent user confusion.
upd
removed read from mre because it is set by default
Problem
When attempting to open a file using
std::fs::OpenOptionswith both.read(true)and.create(true)set, but without.write(true)or.append(true), theopen()method returns anio::Errorwith kind:InvalidInput(corresponding to EINVAL / OS error 22 on Linux).My intention was to create a marker file that indicates the process started, without writing anything into it.
MRE:
playground
Output:
Debugging & Context
Running the above code produces
Error: Os { code: 22, kind: InvalidInput, message: "Invalid argument" }.Because this snippet was part of a larger system, I spent considerable time debugging my logic and, eventually, using
straceto determine what was wrong.straceconfirmed that the error occurs before anyopenoropenatsystem call is attempted for the target file, indicating that the validation failure happens within the Rust standard library.Analysis of the std::fs::sys::unix::fs source reveals a check in
OpenOptions::get_creation_modethat explicitly returnsEINVALifcreate,create_new, ortruncateis set withoutwriteorappendalso being set.Crucially, the underlying Linux syscall does permit this combination, as the following C code demonstrates:
It successfully executes
open(path, O_RDONLY | O_CREAT, 0644).Working C Example:
Output:
Similarly, Python's standard file opening modes can create files for reading (with
"a+"):Documentation Issue
The current documentation for
OpenOptions::openlists potential errors, including:While the failing case is considered an invalid combination by the Rust standard library, it is not explicitly listed. The phrase "etc." does not make it clear that requesting creation inherently requires requesting write or append access within the
OpenOptionsbuilder—even though the underlying OS (like Linux) doesn't enforce this restriction forO_RDONLY | O_CREAT.This can be surprising for users expecting behavior aligned with OS syscalls.
Note
The
.create()method’s documentation does specify that.write(true)or.append(true)must also be set for.create(true)to actually create a file. However, this restriction is not repeated in the.open()documentation, nor is it reflected in error messages. This makes it easy to overlook, leading to confusion and unnecessary debugging.Suggestion
Please consider updating the documentation for
OpenOptions::openand/or theio::ErrorKind::InvalidInputdescription to explicitly state that setting.create(true),.create_new(true), or.truncate(true)also requires.write(true)or.append(true)to be set. This would make the behavior much less surprising for users expecting alignment with the underlying OS.Alternatively, is this validation check actually necessary? If not, perhaps it could be relaxed to match OS behavior. At minimum, returning a more specific error message (rather than "Invalid argument") would also help prevent user confusion.
upd
removed read from mre because it is set by default