Returning bool or Result?

Hi,

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.

Thank you.

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.

1 Like

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.

5 Likes

That's a good point! :grinning:

Anyway Rust compiler is not forcing you to consume Option 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 .

Actually, Result is in fact #[must_use].

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.

1 Like

So in my case it should be better to use Result over Option?

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.

For examples, see std containers: std::collections::HashMap::remove returns Option<V> to give the ownership of the removed value to the caller, while std::collections::HashSet::remove returns bool.

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, _>.

5 Likes

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.

enum TaskRemoveError {
    PermissionError,
    Incomplete,
    InvalidJobId
}
impl JobManager {
    fn take_result(&mut self, id: JobId) -> Result<TaskResult, TaskRemoveError> { ... }
}

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.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.