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

"Clone" kind bound on closures and traits, with autogenerated clone implementation #2830

Closed
bblum opened this issue Jul 7, 2012 · 13 comments
Labels
A-codegen Area: Code generation A-traits Area: Trait system A-typesystem Area: The type system C-enhancement Category: An issue proposing an enhancement or a PR with one. E-hard Call for participation: Hard difficulty. Experience needed to fix: A lot.

Comments

@bblum
Copy link
Contributor

bblum commented Jul 7, 2012

A common idiom might be to create an arc or exclusive with a data structure inside, then spawn lots of worker tasks to walk over it. With #2829, it won't be possible to build a ~-closure and call it multiple times, since ARCs are noncopyable, since they need a special clone() interface.

Add a factory-style interface that uses a clone trait to enable this pattern without the user getting their hands dirty copying around everything in a closure's environment.

@eholk
Copy link
Contributor

eholk commented Jul 7, 2012

Hopefully we can do this with the visitor interface. We start by adding a clone trait, with a default implementation for <A: copy> to make all copyables cloneable. Then we use the visitor to walk closures and generate a clone implementation for functions. If this is really possible, it obviates the need for copy constructors.

@jesse99
Copy link
Contributor

jesse99 commented Dec 26, 2012

This bit me when I updated a library to use pipes. I was relying on being able to copy channels which works with oldcomms but not with pipes::SharedChan. I haven't been able to come up with a good work around (other than dropping pipes). Closest I was able to find is to use MutexARC but that's A) clumsy and B) needs tweaks to the API (see #4292).

Here is an example of the sort of thing I was trying to do:

#[allow(non_implicitly_copyable_typarams)];
use core::send_map::linear::{LinearMap};

extern mod std;

// ---- application code ----------------------------------
fn main()
{
    let state_chan = manage_state();
    let state_chan = pipes::SharedChan(state_chan);

    let mut openers = LinearMap();
    let opener: Opener = |_config, push| {manage_connection(state_chan.clone(), push)};
    openers.insert(~"/uptime", opener);

    run_server(Config {openers: openers});

    libc::funcs::posix88::unistd::sleep(3);
    libc::exit(0);
}

enum StateMesg
{
    AddListener(~str, pipes::Chan<int>),
}

type StatePort = pipes::Port<StateMesg>;
type StateChan = pipes::Chan<StateMesg>;

// This task might be used by a server to manage global state. Incoming
// connections register themselves using AddListener so that they can be 
// notified of state changes. In this case our state is very simple: the number 
// of seconds the server has been up.
fn manage_state() -> StateChan
{
    let (state_port, state_chan): (StatePort, StateChan) = pipes::stream();
    do task::spawn_sched(task::ManualThreads(1)) |move state_port|
    {
        let mut time = 0;
        let mut listeners = LinearMap();
        loop
        {
            time += 1;
            libc::funcs::posix88::unistd::sleep(1);
            for listeners.each_value |ch: &pipes::Chan<int>| {ch.send(time)};

            match state_port.recv()
            {
                AddListener(key, ch) =>
                {
                    listeners.insert(key, ch);
                }
            }
        }
    }
    state_chan
}

// Called for each connection.
fn manage_connection(state_chan: pipes::SharedChan<StateMesg>, push: PushChan)
{
    do task::spawn_sched(task::ManualThreads(1)) |move state_chan, move push|
    {
        let (notify_port, notify_chan) = pipes::stream();
        state_chan.send(AddListener(~"something unique", notify_chan));

        loop
        {
            let secs = notify_port.recv();
            error!("pushing %?", secs);
            push.send(secs.to_str());
        }
    }
}

// ---- server code ---------------------------------------

pub type PushChan = pipes::SharedChan<~str>;
pub type Opener = fn~ (config: Config, push: PushChan);

pub struct Config
{
    // Real code would have a lot more here, including stuff
    // which might be useful to application connections.
    pub openers: LinearMap<~str, Opener>,
}

// This would correspond to some sort of server library entry point.
// Because it is a library it needs to deal with abstract types like
// closures. To keep things simple we don't handle connections closing 
// or cleanly shutting down tasks.
fn run_server(config: Config)
{
    do task::spawn_sched(task::ManualThreads(1)) |move config|
    {
        let (push_port, push_chan) = pipes::stream();
        let push_chan = pipes::SharedChan(push_chan);

        create_connection(&config, &~"/uptime", push_chan);

        loop
        {
            let data = push_port.recv();
            error!("received %s", data);
        }
    }
}

// Called for each connection. Spawns an application task to manage
// the connection.
fn create_connection(config: &Config, name: &~str, push: PushChan)
{
    match config.openers.find_ref(name)
    {
        Some(opener) =>
        {
            // There can be multiple connection tasks so we need to
            // either copy config or share it:
            //
            // 1) Copying the config sounds reasonable and is easy,
            // but copying unique closures requires copying what 
            // they capture which is very dangerous here (and not
            // caught by the compiler atm) because SharedChans
            // are clonable but not copyable.
            //
            // 2) ARC is the next obvious solution. But Config is not
            // Const so that is out.
            //
            // 3) MutexARC relaxes the Const requirement but it's
            // a bit cumbersome to use and only provides mutable
            // access which means we can't get a reference to values
            // in the map without using unsafe code. It does expose
            // the MutexARC state, but it doesn't expose the types
            // and functions used by access so we can't write a const
            // version.
            (*opener)(copy *config, push);
        }
        None =>
        {
            fail fmt!("Couldn't find %s", *name);
        }
    }
}

@bblum
Copy link
Contributor Author

bblum commented Dec 26, 2012

@jesse99 SharedChan should have a clone() method too.

@nikomatsakis
Copy link
Contributor

~fn() is becoming non-copyable, making this a non-issue, at least for now.

@bblum
Copy link
Contributor Author

bblum commented Jul 10, 2013

We've discussed the possibility of having a Clone bound on heap closures / traits, which would in effect grant this capability. It would be slightly more involved than supporting the builtin bounds, since you would have to autogenerate a clone implementation for each such closure that's created, and emit an extra vtable entry for it (in such a way that promoting to a fn/trait without :Clone safely ignores it).

@bblum bblum reopened this Jul 10, 2013
@bblum
Copy link
Contributor Author

bblum commented Jul 10, 2013

Nominating production-ready milestone, I guess? This wouldn't be a backwards-compatibility hazard or anything.

@graydon
Copy link
Contributor

graydon commented Jul 11, 2013

just a bug, removing milestone/nomination.

@Aatch
Copy link
Contributor

Aatch commented Mar 23, 2014

Clone is a standard library trait and there is deriving support for it.

@Aatch Aatch closed this as completed Mar 23, 2014
@huonw
Copy link
Member

huonw commented Mar 23, 2014

@Aatch there's no support of any kind for trait objects or closures yet.

@bblum
Copy link
Contributor Author

bblum commented Mar 23, 2014

it's not as simple as deriving for normal types. to do it for traits and closures will require at least an extra vtable entry in the type representation.

@bblum bblum reopened this Mar 23, 2014
@Aatch
Copy link
Contributor

Aatch commented Mar 23, 2014

Apologies, misread the title slightly.

@nikomatsakis
Copy link
Contributor

I don't think we intend to support Clone as some kind of meta feature. I could imagine, instead, supporting objects with multiple traits, and just creating a meta-vtable that includes both. I'm inclined to close. Note that you can already model this today by making a trait that extends Clone and the other trait you're interested in, though it's somewhat roundabout.

@nikomatsakis
Copy link
Contributor

(In any case, this would require an RFC)

saethlin pushed a commit to saethlin/rust that referenced this issue Apr 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-codegen Area: Code generation A-traits Area: Trait system A-typesystem Area: The type system C-enhancement Category: An issue proposing an enhancement or a PR with one. E-hard Call for participation: Hard difficulty. Experience needed to fix: A lot.
Projects
None yet
Development

No branches or pull requests

7 participants