Problem with Boxed closure

#1

Hi all,

first of all, I’m really sorry for my question, but I tried to solve my problem for last two or three days without success. I try to implement this code:

use std::borrow::Cow;
use smbc::SmbClient;

type AuthType<'a> = for<'b> Fn(&'b str, &'b str) -> (Cow<'a, str>, Cow<'a, str>, Cow<'a, str>) + 'a;

struct Builder<'a> {
    auth: Box<AuthType<'a>>
}

impl<'a> Builder<'a> {
    pub fn new(username: &'a str, password: &'a str, domain: &'a str) -> Builder<'a> {
        let auth = move |host: &str, share: &str| {
            (Cow::from(domain), Cow::from(username), Cow::from(password))
        };

        Builder {
            auth: Box::new(auth)
        }
    }

    fn build(&self) -> Connector {
        Connector::new(self)
    }
}

struct Connector<'a>
{
    client: SmbClient<'a>
}

impl<'a> Connector<'a> {
    pub fn new(builder: &'a Builder) -> Connector<'a> {
        let x: &(for<'b> Fn(&'b str, &'b str) -> (Cow<'a, str>, Cow<'a, str>, Cow<'a, str>)) = &*builder.auth;

        let client = SmbClient::new(x).unwrap();

        Connector { client }
    }
}

Basically I try to use smbc crate (smbc crate) as a SMB client for the next part of the code. For this porpuse I need a closure that is used in phase of authentications. The SmbClient takes the reference of this closure and there is the problem. When I create a boxed closure in Builder struct and use it as Boxed object in Connector struct I get this compiler message:

error[E0277]: the size for values of type `dyn for<'b> std::ops::Fn(&'b str, &'b str) -> (std::borrow::Cow<'_, str>, std::borrow::Cow<'_, str>, std::borrow::Cow<'_, str>)` cannot be known at compilation time
  --> backup_app/src/connectors/builders/smb.rs:77:22
   |
77 |         let client = SmbClient::new(x).unwrap();
   |                      ^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `dyn for<'b> std::ops::Fn(&'b str, &'b str) -> (std::borrow::Cow<'_, str>, std::borrow::Cow<'_, str>, std::borrow::Cow<'_, str>)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
   = note: required by `smbc::smbc::SmbClient::new`

I believe that I understand this message, but please can you suggest my how implement this pattern in other (functional) way? As I said I tried to find hint in many places, but probably I don’t understand some essential knowledge to fix this code.

Thank you very much for your help and have a nice day

0 Likes

#2

SmbClient's function signature is weird. It uses generics, so it works with one concrete type of function, but it still takes it by reference (which is a different type from &dyn reference!).

Try something like SmbClient::new(&|| builder.auth())

1 Like

#3

Yeah, that is weird. I was playing around with something:

fn foo<T: Fn() -> String>(data: T) {
    println!("{}", data());
}
fn main() {
    let bar: Box<dyn Fn() -> String> = Box::new(|| "abc".to_string());
    foo(&*bar);
}

Is fine and compiles seeing as how &dyn Fn() -> String implements Fn() -> String, but does not agree if I change it to this:

fn foo<T: Fn() -> String>(data: &T) { //Only change is that data is now a ref
    println!("{}", data());
}
fn main() {
    let bar: Box<dyn Fn() -> String> = Box::new(|| "abc".to_string());
    foo(&*bar);
}

And this doesn’t compile with the same error as the OP, so I instead added another reference to the call:

foo(&&*bar);

This might not live long enough without lifetime annotations, but this compiles.
Playground


In other words, change your code to be

let x = //...
let client = SmbClient::new(&x).unwrap();
0 Likes

#4

Hi guys,
Thank you very much for your help I’m really appreciated for it.

I tried both of your solutions.


@kornel

I tried to change my code to this:

#![feature(fn_traits)]
use std::borrow::Cow;
use smbc::SmbClient;

type AuthType<'a> = for<'b> Fn(&'b str, &'b str) -> (Cow<'a, str>, Cow<'a, str>, Cow<'a, str>) + 'a;

struct Builder<'a> {
    auth: Box<AuthType<'a>>
}

impl<'a> Builder<'a> {
    pub fn new(username: &'a str, password: &'a str, domain: &'a str) -> Builder<'a> {
        let auth = move |host: &str, share: &str| {
            (Cow::from(domain), Cow::from(username), Cow::from(password))
        };

        Builder {
            auth: Box::new(auth)
        }
    }

    fn build(&self) -> Connector {
        Connector::new(self)
    }
}

struct Connector<'a>
{
    client: SmbClient<'a>
}

impl<'a> Connector<'a> {
    pub fn new(builder: &'a Builder) -> Connector<'a> {
//        let x: &(for<'b> Fn(&'b str, &'b str) -> (Cow<'a, str>, Cow<'a, str>, Cow<'a, str>)) = &*builder.auth;

        let client = SmbClient::new(&move |hostname: &str, domain: &str| builder.auth.call((hostname, domain))).unwrap(); // This line was changed

        Connector { client }
    }
}

Unfortunately, now the compiler shows this kind of error:

error[E0515]: cannot return value referencing temporary value
   --> backup_app/src/connectors/builders/smb.rs:144:9
    |
142 |         let client = SmbClient::new(&move |hostname: &str, domain: &str| builder.auth.call((hostname, domain))).unwrap();
    |                                      ------------------------------------------------------------------------- temporary value created here
143 |
144 |         Connector { client }
    |         ^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

error: aborting due to previous error

For more information about this error, try `rustc --explain E0515`.

If I understand correctly I can’t create reference to the new closure because the new closure exists only in the scope of the new function.


@OptimisticPeach

Next, I tried your suggestion so here is the code with your fix:

use std::borrow::Cow;
use smbc::SmbClient;

type AuthType<'a> = for<'b> Fn(&'b str, &'b str) -> (Cow<'a, str>, Cow<'a, str>, Cow<'a, str>) + 'a;

struct Builder<'a> {
    auth: Box<AuthType<'a>>
}

impl<'a> Builder<'a> {
    pub fn new(username: &'a str, password: &'a str, domain: &'a str) -> Builder<'a> {
        let auth = move |host: &str, share: &str| {
            (Cow::from(domain), Cow::from(username), Cow::from(password))
        };

        Builder {
            auth: Box::new(auth)
        }
    }

    fn build(&self) -> Connector {
        Connector::new(self)
    }
}

struct Connector<'a>
{
    client: SmbClient<'a>
}

impl<'a> Connector<'a> {
    pub fn new(builder: &'a Builder) -> Connector<'a> {
        let x: &(for<'b> Fn(&'b str, &'b str) -> (Cow<'a, str>, Cow<'a, str>, Cow<'a, str>)) = &*builder.auth;

        let client = SmbClient::new(&x).unwrap(); // This line was changed

        Connector { client }
    }
}

Even in this situation the compilation ends with the error:

error[E0515]: cannot return value referencing local variable `x`
   --> backup_app/src/connectors/builders/smb.rs:144:9
    |
142 |         let client = SmbClient::new(&x).unwrap(); // This line was changed
    |                                     -- `x` is borrowed here
143 |
144 |         Connector { client }
    |         ^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

error: aborting due to previous error

Again, I think that there is the same problem as in the previous code.


At the end I tried to implement intermediate struct Test which will resolve this problem with borrows:

use std::borrow::Cow;
use smbc::SmbClient;

type AuthType<'a> = for<'b> Fn(&'b str, &'b str) -> (Cow<'a, str>, Cow<'a, str>, Cow<'a, str>) + 'a;

struct Builder<'a> {
    auth: Box<AuthType<'a>>
}

impl<'a> Builder<'a> {
    pub fn new(username: &'a str, password: &'a str, domain: &'a str) -> Builder<'a> {
        let auth = move |host: &str, share: &str| {
            (Cow::from(domain), Cow::from(username), Cow::from(password))
        };

        Builder {
            auth: Box::new(auth)
        }
    }

    fn build(&self) -> Test {
        Test::new(&*self.auth)
    }
}

struct Test<'b, 'a: 'b> {
    test: &'b AuthType<'a>
}

impl<'b, 'a: 'b> Test<'b, 'a> {
    pub fn new(test: &'b AuthType<'a>) -> Test<'b, 'a> {
        Test { test }
    }

    fn build(&'a self) -> Connector<'a> {
        Connector::new(&self.test)
    }
}

struct Connector<'a>
{
    client: SmbClient<'a>
}

impl<'a> Connector<'a> {
    pub fn new(builder: &'a &'a AuthType<'a>) -> Connector<'a> {
        let client = SmbClient::new(builder).unwrap();

        Connector { client }
    }
}

Now the code is is compiled successfully. So my last question is: Is it possible to implement the same logic without the ugly Test struct?

Once again thank you very much guys.

0 Likes

#5

Unfortunately you would either, as you mentioned, return a reference to a local variable, or end up with a self-referential struct. Solution? Instead of using a lifetime-bound variable to store your function, make it a Fn() + 'static, ie, restrict it to only predetermined functions, so that you end up with no lifetime problems. Unfortunately this doesn’t apply to your case, as you’re creating it at runtime, therefore you could store the username: &'a str, password: &'a str, domain: &'a str parameters in the builder, and then move them to the connector, that way you end up with a Connector::new function that looks like this:

pub fn new(builder: &'a Builder) -> Connector<'a> {
    let (u, p, d) = (builder.username, builder.password, builder.domain);
    let client = SmbClient::new(&move |host: &str, share: &str| {(u, p, d)}).unwrap();

    Connector { client, username: u, password: p, domain: d }
}
0 Likes

#6

Thank you,
I tried your solution and I believe that I undestand your explanation. Unforunatlly the definition of the closure that is used for SmbClient is for<b'> Fn(&'b str, &'b str) -> (Cow<'a, str>, Cow<'a, str>, Cow<'a, str>), so your code will work only if the lifetime 'a is equal to 'static. But this is not my situation. To prove my theory I change my code to:

struct Builder<'a> {
    username: &'a str,
    password: &'a str,
    domain: &'a str,
}

impl<'a> Builder<'a> {
    pub fn new(username: &'a str, password: &'a str, domain: &'a str) -> Builder<'a> {
        Builder {
            username,
            password,
            domain,
        }
    }

    fn build(&self) -> Connector {
        Connector::new(self)
    }
}

struct Connector<'a>
{
    client: SmbClient<'a>,
    username: &'a str,
    domain: &'a str,
    password: &'a str,
}

impl<'a> Connector<'a> {
    pub fn new(builder: &'a Builder) -> Connector<'a> {
        let (u, p, d) = (builder.username, builder.password, builder.domain);

        let client = SmbClient::new(&move |host: &str, share: &str| {
            (Cow::Borrowed(builder.domain),
             Cow::Borrowed(builder.username),
             Cow::Borrowed(builder.password))
        }).unwrap();

        Connector { client, username: u, password: p, domain: d }
    }
}

With this error from compiler:

error[E0515]: cannot return value referencing temporary value
   --> backup_app/src/connectors/builders/smb.rs:148:9
    |
142 |           let client = SmbClient::new(&move |host: &str, share: &str| {
    |  ______________________________________-
143 | |             (Cow::Borrowed(builder.domain),
144 | |              Cow::Borrowed(builder.username),
145 | |              Cow::Borrowed(builder.password))
146 | |         }).unwrap();
    | |_________- temporary value created here
147 |
148 |           Connector { client, username: u, password: p, domain: d }
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

error: aborting due to previous error

So at the end I propablby don’t have any other option than using the intermediate structure for storing reference to closure.

But still thank you very much, because I was really stacked at this problem. Now I have at least one solution.

0 Likes

#7

I think that’s an issue with the SmbClient design. You could file a bug in that project to change their &impl Fn to &dyn Fn, or change &impl Fn to impl Fn and box the closure in SmbClient's new. Or fork it.

0 Likes

#8

I think as @kornel that it is a design problem of SmbClient. The signature is

    pub fn new<F>(auth_fn: &'a F) -> Result<SmbClient<'a>>
        where F: for<'b> Fn(&'b str, &'b str) -> (Cow<'a, str>, Cow<'a, str>, Cow<'a, str>) {

It essentially says that you need to build a function at lifetime 'a, since it uses &'a references and then also a &'a reference to that function. If you need to store the function then the reference is a self-reference, which is forbidden in rust. The signature could have been

    pub fn new<'r,F>(auth_fn: &'r F) -> Result<SmbClient<'a>>
        where F: for<'b> Fn(&'b str, &'b str) -> (Cow<'a, str>, Cow<'a, str>, Cow<'a, str>) {

or some boxing.

If the authors of SmbClient do not want to fix it, or in the meantime, I think you can solve it with one unsafe statement (as alternative to forking it).
playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e637995831f9164b76d47f89319f05b2
This code compiles, although its unsafe is clearly disturbing.

2 Likes

#9

Thank you guys very much. I try to use one of your suggestion in my code.

I learn much new thinks from your answers.

Once again thank you and have a nice day.

0 Likes