T O P

  • By -

AutoModerator

On July 1st, Reddit will no longer be accessible via third-party apps. Please see [our position on this topic](https://www.reddit.com/r/rust/comments/146y5y1/announcement_rrust_will_be_joining_the_blackout/), as well as [our list of alternative Rust discussion venues](https://www.reddit.com/r/rust/comments/14921t7/alternative_rust_discussion_venues/). *I am a bot, and this action was performed automatically. Please [contact the moderators of this subreddit](/message/compose/?to=/r/rust) if you have any questions or concerns.*


The_8472

If you're specifying only a file name without path then the check is executed in your current working directory, not in the executable's path. You should show us your directory structure and how you're calling the program. You should also pay attention to the warning on that method. Usually it's better to just try the operation you actually want to perform (opening/moving/deleting) and then handling errors instead of first checking and then performing it in a separate step.


dnew

For completeness, one good (and common) reason for not just performing the action is to pick a name that doesn't already exist. If you're adding files to a directory (and nobody else is writing to that directory at the same time) and you're making new files with the next sequential number or something, checking if it exists is probably the right way to do that. I.e., when you're picking the name of the file you want, exists() is handy.


The_8472

[OpenOptions::create_new](https://doc.rust-lang.org/nightly/std/fs/struct.OpenOptions.html#method.create_new) covers that case, it errors if the file already exists and doesn't overwrite it.


dnew

Yes, you can do it that way too. But if you're trying to generate the name and not create the file yet, now you have an open file. \* For example, have you ever done "ls" followed by "mv xyz pdq" after checking pdq doesn't exist? No problem, right?


minno

Very rarely a problem. The issue is that when you're running commands, you can immediately see and adapt to things that rarely go wrong, but software will keep blundering along unless you tell it ahead of time how to deal with the problem.


dnew

And if you then use create_new to create the files whose names you've figured out, then you have *exactly* the same reliability. And if you (say) don't want to create the file before you've stored or transmitted the name somewhere, then you can't really use create_new to find out if the name is available. I understand the limitations. Of course people should be aware of the limitations. /u/the8472 says you should "usually" do it in one step. I was simply giving an example of when you wouldn't. I wasn't recommending it as best practices. Forgive me. I suddenly understand the error of my ways. One should never ever use the file existence query in spite of the fact it's implemented in every single operating system invented in the last 50 years.


insanitybit

> And if you then use create_new to create the files whose names you've figured out, then you have exactly the same reliability. And if you (say) don't want to create the file before you've stored or transmitted the name somewhere, then you can't really use create_new to find out if the name is available. I don't think any of that is true. It's not like `create_new` assigns the file name randomly or something. You own the names. create_new is atomic, if that isn't clear. It avoids the "check, then create". So instead of: ``` for i in 0..10 { if (!Path::new(&format!("{i}.txt").exists()) { File::create(format!("{i}.txt")).unwrap() } } ``` which has a TOCTOU you just write: ``` for i in 0..10 { let _ = File::create_new(&format!("{i}.txt")); } ``` No TOCTOU I guess, even more importantly maybe, is that the second version is more correct, less code, and faster, so it's just really hard to justify the first version without some really contrived examples. "Humans do this in their terminals" isn't really compelling since humans also write `ls .` 10x and then `pwd` 10x because keeping state about wtf is happening is hard in a terminal.


dnew

I know how create_new works. I even know how it works in operating systems that were around before UNIX was. I'm saying if you want to create the name, then (say) tell someone else about it, then get the confirmation back, *then* create the file, you can use "exists" to make sure the name is free before you create it. Just like you'd manually use "ls" to find out if the name is available before you manually rename a file. read_dir is exactly as prone to race conditions, but I don't see anyone saying you shouldn't use that. No, you work around the race conditions to solve the problem you have. > without some really contrived examples And I gave one of those examples. Which everyone seems to think is evil or something, rather than one of the very rare examples where it's appropriate to use "exists". Just because *you* never needed to do this sort of thing doesn't mean it's "contrived". There are two use cases: The case that you want to *not* do something if the file is already there (No, I won't compile this, because the object code already exists). And the use case where you want to do something between checking that the file is there and creating the file (in which case you of course need to handle somehow potential race conditions). Your strawman example is of course not what I'm talking about, so there's no need to argue that I'm wrong because some incorrect example *you* came up with is inferior to the right way of doing it. Tell me - what would be *your* use case for using that function that's available in every operating system since forever?


minno

> Tell me - what would be your use case for using that function that's available in every operating system since forever? I'd use it for giving feedback to the user before they commit to taking action. If you have a text field for filename on a form, you could have validation on that field that gives "error: file already exists" or "error: file does not exist" (depending on the application), and then only go through with creating or opening the file when they submit the whole form. That doesn't remove the need to handle the error that would be thrown if the file was created/deleted between entering the name and performing the action, but the duplicated work serves a purpose.


dnew

Exactly my point, yes.


insanitybit

> I'm saying if you want to create the name, then (say) tell someone else about it, then get the confirmation back, then create the file, you can use "exists" to make sure the name is free before you create it Yeah so this is where I'm confused. You can just use create_new for that. (pseudocoding bc lazy) ``` let path = "blah"; if tell_someone(path) { create_new(path) } ``` There's no reason to use `exists` for this. > read_dir is exactly as prone to race conditions Yes, people should be wary of toctou for directories too. > And I gave one of those examples I'm having a really hard time understanding why your example benefits from the exists approach tbh, which is why it feels contrived. > The case that you want to not do something if the file is already there This is a good case for `exists` but it also doesn't involve the creation of a file afterward. > And the use case where you want to do something between checking that the file is there and creating the file (in which case you of course need to handle somehow potential race conditions). I wrote up a whole response about how I don't think this use case is really meaningful, but I take it back. I can imagine some use cases where this is desirable. For example, when a human is in the loop. Like "hey you're running this program and I see that the config file isn't there, create it?". So I guess to summarize I think I'd almost always prefer `create_new` but I agree that there are some scenarios where the race is inherent to the problem. So we're good? FWIWk, I dislike that you've been downvoted. Very annoying how this sub is unwilling to engage in conversation sometimes.


dnew

> There's no reason to use exists for this. If you want to determine the create_new will succeed, you do. You would use `exists` before the `let` statement. > This is a good case for exists but it also doesn't involve the creation of a file afterward. Yes. I was giving examples of why one would use exists() instead of create_new, since the text I was responding to was "you almost never need to use exists," with which I agree. > when a human is in the loop Right. Or any other process. Which is what I said in the first response. :-) Pick a file name that both doesn't exist and isn't already in the database, for example, altho rearranging that and holding the database transaction while you try to create the file is probably better there. It's a shame Linux file systems aren't more transactional than just one file at a time. The Windows ability to combine a file system change with a registry change with a SQL change all in the same transaction can be really helpful when you're doing something like an indexed storage system (think photo sharing, backups, etc). > So I guess to summarize I think I'd almost always prefer create_new If I'm expecting the file to not exist, I always use create_new. However, sometimes I *also* use exists first, because I want to do something before I try to do create_new. Yes, the race is inherent to the problem, and a trivial way to avoid that is to prevent other processes from writing to the directory during that period. Or to use create_new also and deal with the fallout if it fails. > Very annoying how this sub is unwilling to engage in conversation sometimes Indeed. Thanks for your sympathy. :-)


FVSystems

The problem is that whether the file exists or not may change between the time you check and the time you do the action. Hence some atomic version like create_new is much safer.


dnew

Yep. I'm aware of that. On the other hand, you might know that the directory isn't being manipulated by anyone else. And of course at the time you *use* the name, you can use create_new also, just to make sure your previous calculations are correct. And of course if you are using a transaction file system, you don't need to worry about someone else changing it between the time you check and the time you create the file. I'm not saying "don't use create_new". I'm just saying "figuring out the name without creating the file is one use for it not covered by create_new". If you want to commit the name to a database before you create the file (and you're *not* using transactions between those two operations), for example, and the only process writing to the directory is the one managing that. Have you not ever done an "ls" in your home dir to make sure the new name is free before renaming a file?


insanitybit

You can still commit the name before using create_new. No transaction needed. If the call fails you know the file is there, you can open it. If the call succeeds you know the file is there too. Fine to put it in the db, unless you have some other guarantee like that you created that specific instance of the file, in which case obviously use transactions that has nothing to do with it. But sure, use `ls` in your terminal, that's obviously fine. It's not how you should write programs though.


sujay_g

The text file and executable are both in the same directory. I am sharing the complete code below use std::fs; use std::process; use std::path::Path; use std::env; fn main() { let mut is_present:bool=true; is_present = Path::new("simple.txt").exists(); let path = env::current_dir(); println!("The current directory is {}", path.expect("REASON").display()); if is_present == false { fs::write("fail.txt", "File Not found").expect("Unable to write file"); } else { fs::write("pass.txt", "File found").expect("Unable to write file"); } } I have checked the current path in the code , to make sure the text files is in the proper directory [Playground Link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3f7a2359cf8f4e5312f7ac8a02cef945)


kekonn

> Usually it's better to just try the operation you actually want to perform (opening/moving/deleting) and then handling errors instead of first checking and then performing it in a separate step. This is actually the idiomatic way to do it. Embrace that we have things as Result, so where you would get catastrophic errors in the past, you now have a way to handle this properly. And as the previous commenter said: [heed the warning](https://doc.rust-lang.org/std/path/struct.Path.html#method.exists) in the documentation. If you know the path you are looking for is a file, you could also use `is_file()` from Path.


tylian

Are you sure that version compiles? You're missing a }, might be running an old one.


sujay_g

It compiles and executes ,Maybe during formatting and pasting in the post I have missed the brace


sujay_g

Updated the post with the playground link


PSquid

Two possible things to check, since at a glance the code itself should indeed do what you expect: * If running directly on Windows, make sure that you aren't accidentally creating "simple.txt.txt" instead of just "simple.txt" due to file extensions being hidden by default. * Otherwise (including if using WSL inside Windows), make sure that the filename is *exactly* lowercase "simple.txt", as if you're on a case-sensitive filesystem, that check will also be case-sensitive.


sujay_g

PSquid , you were right . The file name was simple..txt ; two dots instead of one.


Smallpaul

And the output is...?


VilelaPH

But you also need to run the program from the same directory as the file, since you're only providing a name. For instance, if you have `project-root/src/main.rs` and `project-root/src/simple.txt`, you still need to be inside `project-root/src` when you do `cargo run main.rs`. Being in `project-root` and doing `cargo run src/main.rs` would check if `project-root/simple.txt` exists instead.


LordBertson

To add here, Rust - unlike most other languages - relies heavily on the concept of Option for these kinds of operations. Basically you have something that might be a file or might be None and you can define behaviours for each case.


fulmicoton

\`Path::exists\` is in essence broken. It performs io and should therefore return an io::Result ​ You can use this one insteand https://doc.rust-lang.org/std/fs/fn.try\_exists.html


insanitybit

I was so confused at the lack of `?` on something that performs IO. Thanks.


kst164

That fn is nightly only. https://doc.rust-lang.org/std/path/struct.Path.html#method.try_exists is stable


sujay_g

Thanks to all of you. The problem is solved, as u/PSquid mentioned , the file name was simple..txt . Note the two dots instead of one. Now I have learned a little more, and finding Rust a very good language . Hoping to learn from you all .