Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

To panic or not to panic when passing incorrect argument types? #845

Open
andreymal opened this issue Aug 8, 2024 · 5 comments
Open

To panic or not to panic when passing incorrect argument types? #845

andreymal opened this issue Aug 8, 2024 · 5 comments

Comments

@andreymal
Copy link
Contributor

An example function:

#[derive(GodotClass)]
#[class(init)]
struct CallMe;

#[godot_api]
impl CallMe {
    #[func]
    fn call_me(a: Gd<Resource>, b: i32) -> Gd<Resource> {
        Resource::new_gd()
    }
}

These calls don't panic and abort the script instead:

# godot-rust function call failed / Bug, call error: #40

var not_a_resource: Variant = Node.new()
CallMe.call_me(not_a_resource, 0)

CallMe.call_me(null, 0)

var overflow_me_untyped: Variant = 9223372036854775807
CallMe.call_me(Resource.new(), overflow_me_untyped)

But these ones panic and do not abort the script (null is returned instead):

# godot-core/src/private.rs:369 - Rust function panicked at godot-core/src/meta/signature.rs:556.
#    Context: CallMe::call_me
#  godot-core/src/private.rs:382 - [panic]  in function `CallMe::call_me` at parameter [0] of type godot_core::obj::gd::Gd<godot_core::gen::classes::resource::re_export::Resource>: `Gd` cannot be null: null
var maybe_resource: Resource = null
CallMe.call_me(maybe_resource, 0)

# godot-core/src/private.rs:369 - Rust function panicked at godot-core/src/meta/signature.rs:556.
#    Context: CallMe::call_me
#  godot-core/src/private.rs:382 - [panic]  in function `CallMe::call_me` at parameter [1] of type i32: `i32` cannot store the given value: 9223372036854775807
var overflow_me_typed := 9223372036854775807
CallMe.call_me(Resource.new(), overflow_me_typed)

I don't know which behavior is correct (but according to API design principles (which contains a broken link btw) and #327 (comment), I assume you are a fan of panics)

But regardless, I expect consistent behavior: panic everywhere or don't panic at all.

(In general, I'd prefer not to panic because I'm trying to compile with panic = "abort" in hopes of reducing binary size, godot-rust binaries are so damn huge...)
(or maybe explicitly disallow panic = "abort" somewhere in the "Getting Started" chapter of the book if you think this sucks)

@Bromeon
Copy link
Member

Bromeon commented Aug 8, 2024

But regardless, I expect consistent behavior: panic everywhere or don't panic at all.

A bit of background here: Godot has two calling conventions -- variant call (varcall) and pointer call (ptrcall). The former is fully dynamic and is used whenever you use reflection, e.g. Object::call(). The latter is used when the method can be dispatched at compile/parse time, and is more efficient.

If you look at the way how Godot defines those in the GDExtension API, you'll come across two signatures:

typedef void (*GDExtensionInterfaceObjectMethodBindCall)(
    GDExtensionMethodBindPtr p_method_bind, 
    GDExtensionObjectPtr p_instance, 
    const GDExtensionConstVariantPtr *p_args, 
    GDExtensionInt p_arg_count, 
    GDExtensionUninitializedVariantPtr r_ret, 
    GDExtensionCallError *r_error
);

typedef void (*GDExtensionInterfaceObjectMethodBindPtrcall)(
    GDExtensionMethodBindPtr p_method_bind, 
    GDExtensionObjectPtr p_instance, 
    const GDExtensionConstTypePtr *p_args, 
    GDExtensionTypePtr r_ret
);

The varcall function pointer offers an output parameter GDExtensionCallError *r_error, while the ptrcall does not.
In other words:

Godot cannot propagate errors in ptrcalls.

This is a limitation of the engine, and the reason why godot-rust can currently not let GDScript abort in such cases. Maybe there is a way -- in case you discover one, I'd be interested to hear!

@Bromeon
Copy link
Member

Bromeon commented Aug 8, 2024

godot-rust binaries are so damn huge.

Try the lazy-function-tables feature, that should help. See lib.rs docs for explanation.

panic = "abort"

You can try this, but I'm not sure how many things break in the library, as it's not designed for it. So use at your own risk.

@Bromeon
Copy link
Member

Bromeon commented Aug 12, 2024

After some discussion with the GDExtension team, it seems like the varcall error handling capability is not intended for domain-specific errors (such as Rust panics), but rather type and arity checking of the arguments.

If we handle Rust panics purely in Rust code and don't tap into the varcall error mechanism, we may be able to make the experience more consistent with ptrcalls.

Closely related: #105 (comment)

@andreymal
Copy link
Contributor Author

godot-rust binaries are so damn huge.

A bit off-topic here but I want to note somewhere that handle_panic_with_print is heavy, replacing its body with return Ok(code()); made my test binary ~200KB smaller (~4KB per #[func])

@lilizoey
Copy link
Member

godot-rust binaries are so damn huge.

A bit off-topic here but I want to note somewhere that handle_panic_with_print is heavy, replacing its body with return Ok(code()); made my test binary ~200KB smaller (~4KB per #[func])

I think this would be useful to mention in a new separate issue. It's not really related to this issue at all. We havent really prioritized binary size very much and we dont even have an issue tracking it specifically, so getting some more conversation started about it would help gauge interest and see how it should be prioritized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants