Skip to content

Implement convenience conversions for Error#1528

Open
CenTdemeern1 wants to merge 2 commits intogodot-rust:masterfrom
CenTdemeern1:master
Open

Implement convenience conversions for Error#1528
CenTdemeern1 wants to merge 2 commits intogodot-rust:masterfrom
CenTdemeern1:master

Conversation

@CenTdemeern1
Copy link
Copy Markdown

This PR implements convenience conversions from Error to Result<(), Error> and back to help with short-circuiting Godot Errors.

Conversions

  • Error to Result<(), Error>: Error::err() and FromGodot
  • Result<(), Error> to Error: Error::from_result() and ToGodot

Why?

Godot's Error is already conceptually similar to Result, having an OK and ERR_* variants, so being able to use the try operator (?) on them would be convenient.

Being able to return Result<(), Error> from a #[func] also helps with this.

Open questions

  • Should Result<(), Error> be a type alias, such as ErrorResult?

Considered alternatives

  • Implementing Try directly is nightly-only.

@CenTdemeern1

This comment was marked as outdated.

These conversions are primarily intended to help short-circuit Godot's `Error`s.

(Revision 2 with documentation adjustments)
@Yarwin Yarwin added feature Adds functionality to the library c: engine Godot classes (nodes, resources, ...) labels Mar 13, 2026
@Bromeon Bromeon added this to the 0.5.x milestone Mar 13, 2026
@GodotRust
Copy link
Copy Markdown

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1528

///
/// To assist with this, `Result<(), Error>` has [a `ToGodot` implementation](ToGodot#impl-ToGodot-for-Result%3C(),+Error%3E)
/// that turns it back into an `Error` on Godot's side, so that it can be used as the return value of `#[func]` functions.
pub fn err(self) -> Result<(), Self> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make more sense to call this into_result? That would also be consistent with from_result.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just .result()? (see also my main comment about _or/_or_else style fns)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to keep it brief because it's meant to be used repeatedly, and I thought it was somewhat similar to Result::err().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meant to be used repeatedly when? In what contexts?

I would also opt for result. For me main usecase for mappings is being able to chain it nicely and make it consistent with other consumers, for example:

        .map(Gd::try_cast::<Node3D>)
        // Semantically – proceed further if casting has been successful.
        .map(Result::ok)
        .flatten()
        .and_then(|n| n.get_world_3d())
        .map(|w| w.get_space())

with your implementation it would be easy to do something along the lines of:

my_return_value
        // Convert to result.
        .err()
        ...
        // Convert to option.
        .ok()
        // if it is ok then we can map it...
        .and_then(...)

or

my_return_value
        // Convert to result.
        .err()
        ...
        // Convert to option.
        .err()
        // if it is err then we must handle it...
        .and_then(...)

Copy link
Copy Markdown
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution!

Regarding naming, the standard library has these functions:

Option<T>::ok_or(err: E)           -> Result<T, E>
Option<T>::ok_or_else(err_func: F) -> Result<T, E>

Result<T, E>::ok()  -> Option<T>
Result<T, E>::err() -> Option<E>

Here, we have

Error::err() -> Result<(), Error>

which I don't think is a good name.

If we want to add any of the _or/_or_else functions later, we should probably name them all as a group.


I'm against implementing GodotConvert and the other traits on Result<(), Error> before we have a resolution on #425. This has a high chance of conflicting with a future blanket impl for Result, for a very niche feature that can easily be implemented via conversion functions.

Comment on lines +89 to +95
/// Converts this `Error` into a `Result<(), Error>` which is `Ok(())` if the given value is `Error::OK`.
///
/// This is a convenience method that may be used to convert this type into one that can be used with the try operator (`?`)
/// for easy short circuiting of Godot `Error`s.
///
/// To assist with this, `Result<(), Error>` has [a `ToGodot` implementation](ToGodot#impl-ToGodot-for-Result%3C(),+Error%3E)
/// that turns it back into an `Error` on Godot's side, so that it can be used as the return value of `#[func]` functions.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too much docs -- the first line is enough, rest could be illustrated with a doctest example instead (a function using ? to return).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing, but that would rely on GodotConvert being implemented for Result<(), Error>.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? The conversion between Error and Result<(), Error> via explicit function is pretty stand-alone. It doesn't even need a running engine, so you can make a real doctest that actually asserts the result.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned before, a function needs to be able to return Result<(), Error> for the try operator to work.

Comment on lines +100 to +108
/// Creates an `Error` from a `Result<(), Error>`, the inverse of [`.err()`](Self::err).
///
/// `Ok(())` becomes `Error::OK`, and `Err(e)` becomes `e`.
pub fn from_result(result: Result<(), Self>) -> Self {
match result {
Ok(()) => Error::OK,
Err(e) => e,
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the use case of from_result()? I don't mean how it's used, but why it is needed.

The Error enum usually comes from engine APIs, so converting it to Result is probably the more typical flow.

Copy link
Copy Markdown
Author

@CenTdemeern1 CenTdemeern1 Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be useful if you ever need to call one of your #[func]s that returns Result<(), Error> from the Rust side. Plus, having conversions going both ways seems beneficial.

Copy link
Copy Markdown
Member

@Bromeon Bromeon Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without implementing ToGodot -- which we currently can't do, see main thread -- #[func] cannot return Result<(), Error> though.

@CenTdemeern1
Copy link
Copy Markdown
Author

CenTdemeern1 commented Mar 13, 2026

I'm against implementing GodotConvert and the other traits on Result<(), Error> before we have a resolution on #425. This has a high chance of conflicting with a future blanket impl for Result, for a very niche feature that can easily be implemented via conversion functions.

Can you elaborate on what you mean by conversion functions? Try blocks aren't stable yet so the only real way to use the try operator in stable Rust is to have the function return Result<(), Error>. (Or to use a hack with a closure I suppose? Which would be super inconvenient)

I'm not against removing this implementation when the blanket implementation happens, though.

@Bromeon
Copy link
Copy Markdown
Member

Bromeon commented Mar 13, 2026

Can you elaborate on what you mean by conversion functions?

into_result + from_result or however they end up being named 🙂
People can just return/accept Error instead of Result<(), Error> without losing any functionality.


I'm not against removing this implementation when the blanket implementation happens, though.

Removing implies a breaking change, which limits us to minor version releases (and even then, it can still break users' code). And that puts an unnecessary constraint on #425, which is otherwise an additive, backwards-compatible change -- for no functional benefit, just minor convenience.

I fully see the convenience around all the impls, but we really need to be careful to not design ourselves into a corner. Having fought a lot with traits and Rust's coherence rule over the last two minor releases, I'm now rather conservative in adding impls. I hope that #425 will allow us to solve a more broader issue, and also make Error nicer to use in the process.

@CenTdemeern1
Copy link
Copy Markdown
Author

into_result + from_result or however they end up being named 🙂 People can just return/accept Error instead of Result<(), Error> without losing any functionality.

This breaks the ability to use the try operator, which is the entire crux of this PR.

@Bromeon
Copy link
Copy Markdown
Member

Bromeon commented Mar 14, 2026

Ah, fair point. Then I'm afraid we have to either solve #425 or find another way. Try is only implemented for a few types and not extensible on stable... maybe something could be hacked with ControlFlow hidden behind type alias, but I'm not sure if that's worth it in the short term 🤔

I do think the to/from Result conversions have merit on their own, so we could limit the current PR to those for now.

@CenTdemeern1
Copy link
Copy Markdown
Author

CenTdemeern1 commented Mar 15, 2026

I’ll probably just document the closure hack for now then and remove the GodotConvert stuff

@Bromeon
Copy link
Copy Markdown
Member

Bromeon commented Mar 28, 2026

@CenTdemeern1 could you update this PR, so we can merge the non-controversial parts already? 🙂

In the meantime, I'll try to give #425 a shot.

@Bromeon
Copy link
Copy Markdown
Member

Bromeon commented Mar 29, 2026

There's now a solution that allows to return Result<T, E> generically:

Let me know if you're still interested in pursuing this PR.

@CenTdemeern1
Copy link
Copy Markdown
Author

Hi, sorry, I got wrapped up in a lot of other things. With the current state of things, should I still make the aforementioned PR changes, or do I need to do something with the new Result PR?

@Bromeon
Copy link
Copy Markdown
Member

Bromeon commented Apr 5, 2026

This depends a bit. What you originally wanted to achieve:

Being able to return Result<(), global::Error> from a #[func]

is now possible with #1544. Is there more we need on top?

@CenTdemeern1
Copy link
Copy Markdown
Author

I made some changes; let me know if I forgot anything

It seems I forgot to clean up after myself
@Bromeon
Copy link
Copy Markdown
Member

Bromeon commented Apr 10, 2026

As mentioned in my previous comment:

Before going further into technicals and naming, it would still be good to show what's the use case of the remaining APIs, now that we have general Result<...> conversion in #[func].

One of the docs mentions "makeshift try blocks", but this doesn't convince me -- it's quite a cumbersome "idiom" and the same might be better achieved with actual try blocks on nightly (which a user should be able to use even if godot-rust does not).

So what exactly do we want to achieve now? 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c: engine Godot classes (nodes, resources, ...) feature Adds functionality to the library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants