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

Syntax to generate bindings for imported function, module, etc... #1293

Closed
joshthoward opened this issue Nov 23, 2020 · 4 comments
Closed

Syntax to generate bindings for imported function, module, etc... #1293

joshthoward opened this issue Nov 23, 2020 · 4 comments

Comments

@joshthoward
Copy link

It would be nice to apply bindings on an imported function like this for example:

// lib.rs
#[pyfunction]
use hello::say_hello;

// hello.rs
pub fn say_hello(name: &str) {
    println!("Hello, {}!", name)
}

Currently this doesn't work:

error: expected `fn`
 --> src/lib.rs:7:1
  |
7 | use hello::say_hello;
  | ^^^

I can do something like:

// lib.rs
use hello::say_hello as other_say_hello;

#[pyfunction]
fn say_hello(name: &str) {
    other_say_hello(name)
}

But is there a nicer syntax to achieve this?

@birkenfeld
Copy link
Member

This can't work since the pyfunction proc-macro needs to see the signature in order to generate the binding code. These macros work only on the item they are applied to, they don't have access to the whole crate's code, or items from other crates.

If there is no way for you to apply the attribute directly in hello.rs, the wrapper function is the best you can do.

@davidhewitt
Copy link
Member

davidhewitt commented Nov 24, 2020

Interestingly enough I was exploring ways to solve this problem on the weekend.

The really interesting thing is that it's possible to have a fn and mod with the same name:

fn foo() { }
mod foo {
    pub const MY_CONST: &str = "const";
}

mod bar {
    use super::foo;
    pub fn bar() {
        foo();
        println!("{}", foo::MY_CONST);
    }
}

The really interesting thing is use super::foo; inside mod bar - that single use statement is accessing both the function and the module.

This means that if we changed #[pyfunction] macro to generate a companion module to each function, instead of additional hidden functions, we could use this as an illusion to make #[pyfunction] items importable across modules.

However, there's a catch. mod declared inside a function (or block) can't access its sibling items, and this means that my experiments over the weekend found that #[pyfunction] broke when used inside another function, and in doctests, with my experimental design. This seems like a non-starter for now.

I filed an issue at rust-lang/rust#79260, and I'm hoping I can energize some consensus on a solution (at least for nightly Rust) which could give us hope to maybe use this pattern in the future in PyO3.

@birkenfeld
Copy link
Member

@davidhewitt if I understand OP correctly they don't want to import a wrapped pyfunction from another module, but apply pyfunction to a non-decorated function imported from another module.

@davidhewitt
Copy link
Member

Oh haha yes that's me finding a nail to hit with the hammer 😆

In which case your answer is definitely correct; it's fundamentally impossible for us to write #[pyfunction] as proposed in the OP for the reasons you already stated.

The closest we could come is probably to feed in the function signature to the attribute:

#[pyfunction(signature = "(&str) -> ()")]
use hello::say_hello;

But I think it's much less clear than just writing a normal #[pyfunction] as well as would be very complicated for us to implement.

Note to OP: you don't have to use the function - you can just refer to it directly:

// lib.rs
#[pyfunction]
fn say_hello(name: &str) {
    hello::say_hello(name)
}

I think that's the best we should aim to offer here and so I'm going to close this one.

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