this post was submitted on 01 Sep 2024
31 points (100.0% liked)

Learn Programming

1631 readers
1 users here now

Posting Etiquette

  1. Ask the main part of your question in the title. This should be concise but informative.

  2. Provide everything up front. Don't make people fish for more details in the comments. Provide background information and examples.

  3. Be present for follow up questions. Don't ask for help and run away. Stick around to answer questions and provide more details.

  4. Ask about the problem you're trying to solve. Don't focus too much on debugging your exact solution, as you may be going down the wrong path. Include as much information as you can about what you ultimately are trying to achieve. See more on this here: https://xyproblem.info/

Icon base by Delapouite under CC BY 3.0 with modifications to add a gradient

founded 1 year ago
MODERATORS
 

For context, I am trying to do a save system for a game.

top 16 comments
sorted by: hot top controversial new old
[–] [email protected] 52 points 2 months ago

I’m sorry that you’re getting differing answers to your question, but I strongly feel that you should attempt to load it and handle the error if it does not exist.

The reason is because if you rely on checking first, it’s always possible that something could happen to the file between when you check and when you perform the load. Plus, you should be prepared to handle the exceptional condition in general. What if you have something strange like the file exists but you don’t have permission to read it?

Because you should implement handling for those errors anyway, you might as well use it as your main means of processing a file that doesn’t exist. That is my suggestion. You can still check before trying to load, but you need to handle the case of the file not existing when you’re loading.

[–] [email protected] 14 points 2 months ago

Generally speaking, there is a race condition lurking where the OS may do whatever to your file you just checked, rendering the check strictly obsolete the moment you get the result. This isn't typical, but possible, and a lovely old-school security vulnerability class. :)

A more practical argument is that you're going to handle any errors your open() may throw, anyway, and therefore it's simply redundant to check for file existence explicitly beforehand.

Under specific circumstances, you may want to do explicit, very specific tests for more detailed error reporting than "error opening file!", for example "save file is corrupted" if it's too short or zero-length, or "save file directory is gone. What the hell, dude? Recreating it, but stop fiddling with my files!"

This is easy to overengineer. Best is to get into the very sensible habit of always checking for errors or exceptions returned by your calls, and this will become a non-issue.

In this particular use-case of save file loading, you might implement displaying a listing of save files in a directory with opendir/readdir or FindFirstFile/FindNextFile and its ilk, to offer a list of files to load, which doubles as a crude existence test already. Many ways lead away from Rome. If you're considering loading an autosave and offer a "Continue" button or something, a cheap existence test would work very well to decide if that button needs to be displayed in the first place, but doesn't free you from handling any open() errors later. You could also open() and validate an autosave directly, and when/if the user decides to "Continue", use the already reserved file descriptor and maybe even the preloaded save data to quickly jump into the game.

If you want a simple answer: Do not introduce race conditions. Always acquire a lock for a shared resource before doing anything with it.

[–] [email protected] 7 points 2 months ago

For a game I don't think it's the end of the world, but you could end up in a situation where the first check passed, then you go to use the file and that fails, so you end up having to handle the "can't use file" case twice anyway. But for something like showing a "Continue" menu item you obviously need to check that there's an existing save to begin with before loading it.

In general checking first leads to race conditions known as "time-of-check to time-of-use", the pitfalls of which can vary greatly, but realistically aren't a problem for a lot of cases.

[–] [email protected] 7 points 2 months ago

Well you need to try and catch when getting the file anyways, it's probably very rare but imagine a scenario of:

  • Check if file exists
  • user deletes file in between
  • (try) opening the file

Or the file could exist, but you don't have permissions to actually open it.

So a bunch of languages / already have their own "try open file"

[–] [email protected] 5 points 2 months ago

General wisdom is that if you can perform some kind of pre-validation action to prevent an exception from occurring, you should do that, rather than expect the exception and handle it, as part of "normal" flow control.

However.

Some types of exceptions, especially when related to itneracting with shared/external systems, cannot be conpletely avoided. Checking for the existence of a file is the textbook example of this. No matter how much you check of the existence of the file, it could technically be deleted or exclusively locked by another process before you get a chance to actually open it.

For all intents and purposes, that's not really likely to happen, so by all means, check for the file, to keep your code sensible, but make sure you have a general strategy for exception handling in place as well.

[–] [email protected] 5 points 2 months ago

You should do both. Creation and catching of exceptions can be an expensive process and even if the check for the file succeeds the loading of the file may not in which case you'll need to handle the exceptions anyway.

[–] [email protected] 3 points 2 months ago

If you're already using an existing engine this is already baked in.

But a new engine I would suggest confirming that the file is there and that there is no handles holding it.

[–] [email protected] 3 points 2 months ago (1 children)

This depends quite a bit on your language and framework/filesystem-library. Could you add what those are?

[–] [email protected] 3 points 2 months ago (1 children)

I'm using Rust and Bevy, with bevy_moonshine for saving

[–] [email protected] 3 points 2 months ago

Do you mean moonshine_save? Does it even provide an API for loading that doesn't return a Result with a possible LoadError?

Rust doesn't generally "throw" errors, it returns them; and generally, function APIs will guide you in the right direction. You generally should not use unwrap() or expect() in your finished code (though the unwrap_or... variants are fine), which means that you must handle all errors the API can return programmatically (possibly just by propagating with ?, if you will instead be handling the error in the caller).

[–] [email protected] 3 points 2 months ago

File existing and we're able to read it are two quite different conditions

[–] [email protected] 1 points 2 months ago

I would explicitly check in this case - but this might not be the right way if you were doing something else, like updating a row in a table millions of times a second. It’s always context dependent.

[–] [email protected] 1 points 1 month ago

Depends, are you using raw os APIs?

If so stop it and use a library. A library will do all the necessary checks anyways, atomically. And you need to handle API errors anyways. So checking ahead of time just would be a waste of time.

But in the end it's irrelevant. This all seems like premature optimization at best and optimizing the wrong thing at worst. You shouldn't need to open thousands of files a second to save a game and if you don't then the cost of all this checking is essentially zero.

And if you are actually bound by the speed of opening files then you should investigate ways to open fewer files (or not closing files that you know will be needed later).

[–] [email protected] 0 points 2 months ago (1 children)

I saw long time ago from somewhere saying that handling exception is expensive in terms of stack operations. To avoid the unexpected I guess you should do both, but a check before loading just saves you from unnecessary exception handlings which, if the very first statement is indeed true, would harm the performance.

[–] [email protected] 2 points 1 month ago* (last edited 1 month ago)

It is an expensive operation as it needs to unwind the stack to get the stack trace.

But if you're checking a single file you won't notice it.

If you're doing it in a tight loop it'll be very noticeable.

Checking the file exists also has a cost.

I likely wouldn't bother if I was saving a single file especially as there are other exceptions that could be thrown in the process.

[–] [email protected] 0 points 2 months ago

I would use both.