I have a function that takes an id and delete the corresponding element from a container, I was wondering if it is better/idiomatic (and why) to return a bool or a Result to confirm whether the operation has been completed successfully.
I would choose to return Result.
For creating Error variant, at first, it is easier to use anyhow to just return Result<T>
Then after all of the variants are determined, thiserror could be used to easily create the Error for the Result.
Containers from the standard library usually return Option<V> here, with Some(value) indicating that value was removed, and None indicating that it wasn't present.
Anyway Rust compiler is not forcing you to consumeOption or Result, so why is better to return them instead of bool?
Do not get me wrong, I also prefer the Option\Result solution (it looks more idiomatic), but I would like to understand if there are any real benefits doing that over returning a bool .
bool has exactly one bit of information: if the operation failed, it can't possibly tell you why or how it failed. That's why you should return Result: it allows you to be exact about the circumstances of the failure.
Deleting an entry from a key-value container is essentially "take" or "pop" operation, rather than simple "remove".
So, returning Option<V> or Result<V, _> with the value's ownership is necessary if users (possibly) wants the removed value, in order to allow the caller to get the value with the ownership.
In contrast, if the key and the value is always identical or equal (e.g. sets rather than maps), returning bool is enough since the caller already knows what value is to be removed.
I think it depends on your situation and context which one to choose (Option vs. Result).
If it is usual and expected that the specified entry does not exist, you may use Option<V>. Most of generic container types will be this type.
If the container expects the element to be removed to exist (and an attempt to remove nonexistent entry is an "error"), you may return Result<T, _>.
Yes, this is important.
For example, assume you are implementing asynchronous task manager TaskManager, and you need TaskManager::take_result(id: JobId) -> /*Some result*/.
It is usual if the task is still running and not yet complete, but it is not only the reason take_result() cannot return the task result.
It may fail due to incomplete task, nonexistent task, or permission error.
In such case, you'd want to use Result, even when it is not always expected to succeed in normal program.
If you want to distinguish real "error" and "cannot accomplish but not an error", you can use Result<Option<_>, _>.
enum TaskRemoveError {
PermissionError,
InvalidJobId
}
impl JobManager {
// If the task is not yet finished, `Ok(None)` is returned.
fn take_result(&mut self, id: JobId) -> Result<Option<TaskResult>, TaskRemoveError> { ... }
}
Result<Option<T>, E> and Option<Result<T, E>> is often used and not redundant. Result::transpose and Option::transpose are implemented to support using such types conveniently.