Rust中

时间:2016-04-30 09:00:52

标签: callback rust event-driven

我想知道如何在Rust中使用带有回调的可组合事件驱动设计。从我现有的实验中,我开始怀疑Rust中的所有权系统更适合自上而下的过程代码,并且在事件驱动设计中对回调所需的父对象的引用存在问题。

基本上,我希望看到以下C ++代码的Rust等价物。该代码实现了一个EventLoop,它使用busy循环调度Timer事件,Timer类具有timer_expired回调,以及一个以500ms为间隔调度计时器的User类。

#include <stdio.h>
#include <assert.h>
#include <list>
#include <chrono>
#include <algorithm>

using namespace std::chrono;

// Wrapping code in System class so we can implement all functions within classes declarations...
template <typename Dummy=void>
struct System {
    class Timer;

    class EventLoop {
        friend class Timer;

    private:
        std::list<Timer *> m_running_timers;
        bool m_iterating_timers;
        typename std::list<Timer *>::iterator m_current_timer;

        void unlink_timer (Timer *timer)
        {
            auto it = std::find(m_running_timers.begin(), m_running_timers.end(), timer);
            assert(it != m_running_timers.end());
            if (m_iterating_timers && it == m_current_timer) {
                ++m_current_timer;
            }
            m_running_timers.erase(it);
        }

    public:
        EventLoop()
        : m_iterating_timers(false)
        {
        }

        milliseconds get_time()
        {
            return duration_cast<milliseconds>(system_clock::now().time_since_epoch());
        }

        void run()
        {
            while (true) {
                milliseconds now = get_time();

                m_iterating_timers = true;
                m_current_timer = m_running_timers.begin();

                while (m_current_timer != m_running_timers.end()) {
                    Timer *timer = *m_current_timer;
                    assert(timer->m_running);

                    if (now >= timer->m_expire_time) {
                        m_current_timer = m_running_timers.erase(m_current_timer);
                        timer->m_running = false;
                        timer->m_callback->timer_expired();
                    } else {
                        ++m_current_timer;
                    }
                }

                m_iterating_timers = false;
            }
        }
    };

    struct TimerCallback {
        virtual void timer_expired() = 0;
    };

    class Timer {
        friend class EventLoop;

    private:
        EventLoop *m_loop;
        TimerCallback *m_callback;
        bool m_running;
        milliseconds m_expire_time;

    public:
        Timer(EventLoop *loop, TimerCallback *callback)
        : m_loop(loop), m_callback(callback), m_running(false)
        {
        }

        ~Timer()
        {
            if (m_running) {
                m_loop->unlink_timer(this);
            }
        }

        void start (milliseconds delay)
        {
            stop();
            m_running = true;
            m_expire_time = m_loop->get_time() + delay;
            m_loop->m_running_timers.push_back(this);
        }

        void stop ()
        {
            if (m_running) {
                m_loop->unlink_timer(this);
                m_running = false;
            }
        }
    };

    class TimerUser : private TimerCallback {
    private:
        Timer m_timer;

    public:
        TimerUser(EventLoop *loop)
        : m_timer(loop, this)
        {
            m_timer.start(milliseconds(500));
        }

    private:
        void timer_expired() override
        {
            printf("Timer has expired!\n");
            m_timer.start(milliseconds(500));
        }
    };
};

int main ()
{
    System<>::EventLoop loop;
    System<>::TimerUser user(&loop);
    loop.run();
    return 0;
}

代码作为标准C ++ 14工作,我相信是正确的。注意,在一个严肃的实现中,出于性能原因,我会使running_timers成为一个侵入式链表而不是std :: list。

以下是此解决方案的一些属性,我需要在Rust等效项中看到:

  • 可以无限制地添加/删除定时器,对分配定时器的方式/位置没有限制。例如,可以使用自己的计时器动态管理每个类的列表。
  • 在timer_callback中,被回叫的类具有完全自由地访问自身,并且正在调用它的计时器,例如,重启它。
  • 在timer_callback中,被调用的类也可以自由删除自身和计时器。 EventLoop了解这种可能性。

我可以展示一些我尝试的东西,但我认为它不会有用。我所遇到的主要痛点是满足借用规则以及对回调特征所涉及的父对象的所有引用。

我怀疑RefCell或类似的东西可能是解决方案的一部分,可能是一个或多个带有内部不安全部件的特殊类,它们可以将东西粘合在一起。也许Rust提供的参考安全的某些部分只能在运行时通过恐慌来保证。

更新

我在Rust中创建了prototype implementation这个,但它不安全。具体做法是:

  • 不得移动计时器和EventLoop。如果它们意外移动,则由于使用指向这些指针而发生未定义的行为。在Rust中甚至无法检测到这一点。
  • 回调实现是一个黑客,但应该工作。请注意,这允许同一个对象接收来自两个或多个计时器的回调,如果将traits用于回调,这是不可能的。
  • 由于使用指向要接收回调的对象的指针,回调是不安全的。
  • 理论上,对象可以自行删除,但这在Rust中似乎不安全。因为如果定时器回调最终会自行删除,那么在某一点上会有一个无效的自我引用。这种不安全的根源是使用指针进行回调。

0 个答案:

没有答案