You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Iced框架映射Subscription触发‘closure is capturing’ panic求助

问题:Iced GUI激光指针程序触发Panic

我正在构建一个模拟激光游戏的简单GUI,红点会在不同时刻以不同速度跟随隐形目标,靠近鼠标光标时会逃离。使用Iced 0.13.1和fastrand 2.3.0,运行代码时主线程触发panic,错误提示:

thread 'main' panicked at ...: the closure laser_pointer::subscription::{{closure}} provided in `Subscription::map` is capturing

怀疑自定义subscription函数存在生命周期问题,尝试用Arc<Mutex<LaserPointerApp>>共享所有权但无效,求解决方案。

完整错误信息

finished `dev` profile [unoptimized + debuginfo] target(s) in 28.23s
     Running `target\debug\laser_pointer.exe`
thread 'main' panicked at C:\Users\hp\.cargo\registry\src\index.crates.io-6f17d22bba15001f\iced_futures-0.13.2\src\subscription.rs:249:9:
the closure laser_pointer::subscription::{{closure}} provided in `Subscription::map` is capturing
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\laser_pointer.exe` (exit code: 101)

原代码

use iced::{Point, Element, Subscription, Event, Rectangle, window, Color, Length, Renderer, Theme};
use iced::widget::canvas::{self, Frame, Geometry, Path};
use iced::mouse::Cursor;
use fastrand;
use std::cell::RefCell;

#[derive(Default, Clone, Copy)]
struct LaserPointer {
    x: f32,
    y: f32,
    speed: Speed,
    imaginary_target: Point,
}

#[derive(Clone, Copy, Default)]
enum Speed {
    #[default]
    Slow,
    Fast,
    CrazyFast,
    Still,
}

#[derive(Debug, Clone)]
enum Message {
    MovePointer(Point),
    UpdateFrame(Rectangle),
}
#[derive(Default, Clone)]
struct LaserPointerApp {
    pointer: LaserPointer,
    rectangle: Rectangle,
}
//Implementing From here isn’t essential, but it helps the following code be a bit cleaner.
impl From<LaserPointer> for Point {
        fn from(pointer: LaserPointer) -> Self {
            Point {
                x: pointer.x,
                y: pointer.y,
            }
        }
}
//We define this function because iced does not have an equivalent method
fn get_bottom_right(rect: Rectangle) -> Point {
    Point {
        x: rect.x + rect.width,
        y: rect.y + rect.height,
    }
}

impl LaserPointer {
    fn new() -> Self {
        Self {
        x: 50.0,
        y: 50.0,
        speed: Speed::default(),
        imaginary_target: Point { x: 50.0, y: 50.0 },
        }
    }

    //This method now handles the random laser pointer movement when the mouse arrow gets too close.
    fn random_movement(&mut self, amount: f32) {
        if fastrand::bool() {
            self.x += fastrand::f32() * amount;
            } else {
            self.x -= fastrand::f32() * amount;
            }
            if fastrand::bool() {
                self.y += fastrand::f32() * amount;
            } else {
                self.y -= fastrand::f32() * amount;
            }
    }
    //We don’t want the speed to change too frequently (cats get bored when a laser pointer moves too quickly), so
    //we’ll use a random f32 from 0.0 to 1.0 and only change when the number is greater than 0.98. In practice, this
    //will mean a speed change every few seconds. The following try_change_target() changes the invisible target
    //for the pointer in the same way.
    fn try_change_speed(&mut self) {
            use Speed::*;
            if fastrand::f32() > 0.98 {
                self.speed = match fastrand::u8(0..3) {
                    0 => Still,
                    1 => Slow,
                    2 => Fast,
                    _ => CrazyFast,
            }
        }
    }
    fn try_change_target(&mut self, rect: Rectangle) {
        let bottom_right = get_bottom_right(rect);
        if fastrand::f32() > 0.98 {
            self.imaginary_target = Point {
            x: fastrand::f32() * bottom_right.x,
            y: fastrand::f32() * bottom_right.y,
            }
        }
    }
    fn change_speed(&self) -> f32 {
        match self.speed {
            Speed::Still => 0.0,
            Speed::Slow => 0.05,
            Speed::Fast => 0.1,
            Speed::CrazyFast => 0.3,
        }
    }
    //Finally, we have this method to move the laser pointer once every loop. One of the speeds is 0.0,
    //though, so it will stay absolutely still in that case.
    fn move_self(&mut self) {
        let x_from_target = self.imaginary_target.x - self.x;
        let y_from_target = self.imaginary_target.y - self.y;
        self.x += fastrand::f32() * x_from_target * self.change_speed();
        self.y += fastrand::f32() * y_from_target * self.change_speed();
    }
}

impl LaserPointerApp{
fn new() -> Self {
    LaserPointerApp {
        pointer: LaserPointer::new(),
        rectangle: Rectangle {
            x: 10.0,
            y: 10.0,
            width: 800.0,
            height: 600.0,
        },
    }
}
}

fn subscription(laserpointer: LaserPointerApp) -> Subscription<Message> {
    iced::event::listen().map(move |event| match event {
        Event::Mouse(iced::mouse::Event::CursorMoved{position})=>Message::MovePointer(position),
        Event::Window(window::Event::Resized (resize_info)) => {
            
            Message::UpdateFrame(Rectangle {
                x: 0.0,
                y: 0.0,
                width: resize_info.width as f32,
                height: resize_info.height as f32,
            })
        }
        _ => {
            Message::UpdateFrame(laserpointer.rectangle)}
    })
}

impl canvas::Program<Message> for LaserPointerApp {
    type State = ();

    fn draw(&self, _state: &Self::State, renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor) 
    -> Vec<Geometry> {
        let mut frame = Frame::new(renderer,bounds.size());

        // Draw the red laser pointer
        let path = Path::circle(Point::new(self.pointer.x, self.pointer.y), 20.0);
        frame.fill(&path, Color::from_rgb(1.0, 0.0, 0.0));

        vec![frame.into_geometry()]
    }
}

fn update(laserpointer: &mut LaserPointerApp, message: Message) {
    match message {
        Message::MovePointer(pos) => {
        if (laserpointer.pointer.x - pos.x).abs() < 20.0 && (laserpointer.pointer.y - pos.y).abs() < 20.0 {
            laserpointer.pointer.random_movement(50.0);
        }
    },
        Message::UpdateFrame(new_bounds) => {
            laserpointer.rectangle = new_bounds;
            laserpointer.pointer.try_change_speed();
            laserpointer.pointer.try_change_target(laserpointer.rectangle);
            laserpointer.pointer.move_self();

        }
}
}

fn view(laserpointer: &LaserPointerApp) -> Element<Message> {
    iced::widget::Canvas::new(laserpointer)
        .width(Length::Fill)
        .height(Length::Fill)
        .into()
}

fn main() -> iced::Result {
    let laserpointer = LaserPointerApp::new();
    iced::application("Awesome laser pointer",update,view)
    .subscription(move |app| subscription(laserpointer.clone()))
    .run()
}

解决方案

问题根源

panic的核心原因是subscription函数中传入的LaserPointerApp实例被闭包捕获,但Iced的Subscription::map要求闭包必须满足'static生命周期,同时不能捕获可能导致所有权冲突的变量。原代码中,闭包在_分支引用了laserpointer.rectangle,这种捕获方式会让闭包的生命周期与laserpointer绑定,不符合Iced订阅系统的要求。

另外,原代码试图通过订阅所有事件来触发指针更新,这种设计不合理——应该用定时订阅来驱动指针的持续移动,而不是依赖无关事件。

修复步骤

  1. 移除订阅中对LaserPointerApp的捕获:订阅只负责传递鼠标移动、窗口调整事件,不需要持有应用实例。
  2. 添加定时更新订阅:用iced::time::every创建定时事件,驱动指针的移动、速度和目标更新。
  3. 调整Message枚举:新增Tick消息用于定时更新。
  4. 重构update逻辑:将指针的自动更新逻辑移到Tick消息分支中。

修复后的完整代码

use iced::{
    time::{self, Duration},
    Point, Element, Subscription, Event, Rectangle, window, Color, Length, Renderer, Theme,
};
use iced::widget::canvas::{self, Frame, Geometry, Path};
use iced::mouse::Cursor;
use fastrand;

#[derive(Default, Clone, Copy)]
struct LaserPointer {
    x: f32,
    y: f32,
    speed: Speed,
    imaginary_target: Point,
}

#[derive(Clone, Copy, Default)]
enum Speed {
    #[default]
    Slow,
    Fast,
    CrazyFast,
    Still,
}

#[derive(Debug, Clone)]
enum Message {
    MovePointer(Point),
    UpdateFrame(Rectangle),
    Tick, // 新增定时更新消息
}

#[derive(Default, Clone)]
struct LaserPointerApp {
    pointer: LaserPointer,
    rectangle: Rectangle,
}

impl From<LaserPointer> for Point {
    fn from(pointer: LaserPointer) -> Self {
        Point {
            x: pointer.x,
            y: pointer.y,
        }
    }
}

fn get_bottom_right(rect: Rectangle) -> Point {
    Point {
        x: rect.x + rect.width,
        y: rect.y + rect.height,
    }
}

impl LaserPointer {
    fn new() -> Self {
        Self {
            x: 50.0,
            y: 50.0,
            speed: Speed::default(),
            imaginary_target: Point { x: 50.0, y: 50.0 },
        }
    }

    fn random_movement(&mut self, amount: f32) {
        if fastrand::bool() {
            self.x += fastrand::f32() * amount;
        } else {
            self.x -= fastrand::f32() * amount;
        }
        if fastrand::bool() {
            self.y += fastrand::f32() * amount;
        } else {
            self.y -= fastrand::f32() * amount;
        }
    }

    fn try_change_speed(&mut self) {
        use Speed::*;
        if fastrand::f32() > 0.98 {
            self.speed = match fastrand::u8(0..3) {
                0 => Still,
                1 => Slow,
                2 => Fast,
                _ => CrazyFast,
            };
        }
    }

    fn try_change_target(&mut self, rect: Rectangle) {
        let bottom_right = get_bottom_right(rect);
        if fastrand::f32() > 0.98 {
            self.imaginary_target = Point {
                x: fastrand::f32() * bottom_right.x,
                y: fastrand::f32() * bottom_right.y,
            };
        }
    }

    fn change_speed(&self) -> f32 {
        match self.speed {
            Speed::Still => 0.0,
            Speed::Slow => 0.05,
            Speed::Fast => 0.1,
            Speed::CrazyFast => 0.3,
        }
    }

    fn move_self(&mut self) {
        let x_from_target = self.imaginary_target.x - self.x;
        let y_from_target = self.imaginary_target.y - self.y;
        self.x += fastrand::f32() * x_from_target * self.change_speed();
        self.y += fastrand::f32() * y_from_target * self.change_speed();
    }
}

impl LaserPointerApp {
    fn new() -> Self {
        LaserPointerApp {
            pointer: LaserPointer::new(),
            rectangle: Rectangle {
                x: 10.0,
                y: 10.0,
                width: 800.0,
                height: 600.0,
            },
        }
    }
}

// 重构订阅函数,不再捕获应用实例
fn subscription() -> Subscription<Message> {
    Subscription::batch([
        iced::event::listen().map(|event| match event {
            Event::Mouse(iced::mouse::Event::CursorMoved { position }) => Message::MovePointer(position),
            Event::Window(window::Event::Resized(resize_info)) => Message::UpdateFrame(Rectangle {
                x: 0.0,
                y: 0.0,
                width: resize_info.width as f32,
                height: resize_info.height as f32,
            }),
            _ => Message::Tick, // 无关事件不触发UpdateFrame,改用Tick驱动
        }),
        // 定时订阅,每16ms触发一次(约60帧)
        time::every(Duration::from_millis(16)).map(|_| Message::Tick),
    ])
}

impl canvas::Program<Message> for LaserPointerApp {
    type State = ();

    fn draw(&self, _state: &Self::State, renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry> {
        let mut frame = Frame::new(renderer, bounds.size());

        let path = Path::circle(Point::new(self.pointer.x, self.pointer.y), 20.0);
        frame.fill(&path, Color::from_rgb(1.0, 0.0, 0.0));

        vec![frame.into_geometry()]
    }
}

fn update(laserpointer: &mut LaserPointerApp, message: Message) {
    match message {
        Message::MovePointer(pos) => {
            if (laserpointer.pointer.x - pos.x).abs() < 20.0 && (laserpointer.pointer.y - pos.y).abs() < 20.0 {
                laserpointer.pointer.random_movement(50.0);
            }
        }
        Message::UpdateFrame(new_bounds) => {
            laserpointer.rectangle = new_bounds;
        }
        Message::Tick => {
            // 在定时tick中处理指针的自动更新逻辑
            laserpointer.pointer.try_change_speed();
            laserpointer.pointer.try_change_target(laserpointer.rectangle);
            laserpointer.pointer.move_self();
        }
    }
}

fn view(laserpointer: &LaserPointerApp) -> Element<Message> {
    iced::widget::Canvas::new(laserpointer)
        .width(Length::Fill)
        .height(Length::Fill)
        .into()
}

fn main() -> iced::Result {
    iced::application("Awesome laser pointer", update, view)
        .subscription(|_| subscription()) // 不再传递应用实例给订阅
        .run()
}

关键修改说明

  • 新增Tick消息:用于定时驱动指针的移动、速度和目标更新,避免依赖无关事件。
  • 重构subscription函数:不再接收LaserPointerApp参数,改用Subscription::batch合并事件监听和定时订阅,闭包不再捕获应用实例,满足'static要求。
  • 调整update逻辑:将指针自动更新的逻辑从UpdateFrame移到Tick分支,窗口调整仅更新边界。
  • 移除不必要的RefCell:原代码中RefCell未被使用,直接删除。

内容的提问来源于stack exchange,提问作者karim abousselham

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 05:56:25