我正在从系统编程的C/C++背景过渡到Rust.完成官方书籍和Rustlings课程后,我决定移植nappgui库(https://github.com/frang75/nappgui),以巩固我对Rust的理解.

nappgui中经常使用一种特定模式,我很难将其有效地翻译为Rust.该模式涉及可变borrow ,当我当前的实现进行编译时,它会引发与这些borrow 相关的运行时错误.

这是我最后try 的一个最小示例(尽管它可能不能完美反映原始的nappgui代码):

//////////////////////////////////////////////////////////
// Platform specific OSAPP library crate
// e.g. osapp_win.rs

pub struct OSApp {
    abnormal_termination: bool,
    with_run_loop: bool,
}

pub fn init_imp(with_run_loop: bool) -> Box<OSApp> {
    Box::new(OSApp {
        abnormal_termination: false,
        with_run_loop: with_run_loop,
    })
}

pub fn run(app: &OSApp, on_finish_launching: &mut dyn FnMut()) {
    on_finish_launching();

    if app.with_run_loop {
        // Following line commented out to simplify
        // osgui::message_loop();
        i_terminate(app);
    }
}

fn i_terminate(_app: &OSApp) {
    // Calls more client callbacks
}


//////////////////////////////////////////////////////////
// OSAPP crate

use core::f64;
use std::{cell::RefCell, rc::Rc};

struct App {
    osapp: Option<Box<OSApp>>,
    _lframe: f64,
    func_create: FnAppCreate,
}

pub trait ClientObject {}
type FnAppCreate = fn() -> Box<dyn ClientObject>;

pub fn osmain(lframe: f64, func_create: FnAppCreate) {
    let app = Rc::new(RefCell::new(App {
        osapp: None,
        _lframe: lframe,
        func_create: func_create,
    }));

    let osapp: Box<OSApp> = osapp_init(true);

    let tmp_a = app.clone();
    tmp_a.as_ref().borrow_mut().osapp = Some(osapp);

    let tmp_b = app.clone();
    let on_finish_launch = || {
        // I understand why I get the already borrowed mutable error here
        i_OnFinishLaunching(&tmp_b.as_ref().borrow());
        // ^^^^^^^^^^^^^^^^^^^^^^^^^
    };

    let tmp_c = &app.as_ref().borrow_mut().osapp;
    if let Some(osapp) = tmp_c {
        /*osapp::*/
        run(&osapp, &mut &on_finish_launch);
    }
}

fn osapp_init(with_run_loop: bool) -> Box<OSApp> {
    /*osapp::*/
    init_imp(with_run_loop)
}

fn i_OnFinishLaunching(app: &App) {
    (app.func_create)();
}

//////////////////////////////////////////////////////////
// main.rs

struct Application {
    // widgets go here
}

impl ClientObject for Application {}

impl Application {
    fn create() -> Box<dyn ClientObject> {
        let mut app = Box::new(Application {
            // Create all the widgets here
        });

        app
    }
}

fn main() {
    /*osapp::*/
    osmain(0.0, Application::create);
}

Output:

thread 'main' panicked at src/main.rs:55:45:
already mutably borrowed: BorrowError
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

我很感激有关如何重新设计或实现代码以避免这些可变borrow 错误的指导.任何特定于移植涉及可变borrow 到Rust的C++模式的见解都会特别有帮助.

Update

上面是整个项目中使用的建筑模式的实例.C中的一般模式如下:

#include <stdio.h>
#include <stdlib.h>

typedef void(*CB1_T)(void*) ;
typedef void(*CB2_T)(void*) ;

struct LowLevelObject {
    void *listener;
    // state data
    CB1_T callback1;
    CB2_T callback2;
};

void low_api1(struct LowLevelObject *lo)
{
    // some functionality
    lo->callback1(lo->listener);
    // more functionality
}

void low_api2(struct LowLevelObject *lo)
{
    // some functionality
    lo->callback2(lo->listener);
    // more functionality
}

void low_api3(struct LowLevelObject *lo)
{
    // some functionality
    printf("%s\n", __func__);
}


struct LowLevelObject *low_create(
    void *listener,
    CB1_T callback1,
    CB2_T callback2)
{
    struct LowLevelObject *lo = calloc(1, sizeof(struct LowLevelObject));
    lo->listener = listener;
    lo->callback1 = callback1;
    lo->callback2 = callback2;
    
    return lo;
}

void low_destroy(struct LowLevelObject *lo)
{
    free(lo);
}

/////////////////////////////////////////

struct HighLevelObject {
    struct LowLevelObject *low;
    // State data
};

static void on_callback1(struct HighLevelObject *hi)
{
    printf("%s\n", __func__);
    low_api3(hi->low);
}

static void on_callback2(struct HighLevelObject *hi)
{
    printf("%s\n", __func__);
    low_api3(hi->low);
}

struct HighLevelObject *high_create()
{
    struct HighLevelObject *hi = calloc(1, sizeof(struct HighLevelObject));
    
    hi->low = low_create(hi, (CB1_T)on_callback1, (CB2_T)on_callback2);
    // NULL checks ignored for simplicity
    return hi;
}

void high_destroy(struct HighLevelObject *hi)
{
    low_destroy(hi->low);
    free(hi);
}

void hi_start()
{
    struct HighLevelObject *hi = high_create();
    low_api1(hi->low);
    low_api2(hi->low);
    high_destroy(hi);
}

////////////////////////////////

int main() {
    hi_start();
    
    return 0;
}

推荐答案

这确实很难在Rust中建模.让我们分析一下这里有什么:

我们有两个包,lowhigh,它们相互依赖,形成一个循环.因为low包不知道high,所以它们在数据 struct 中使用类型擦除和循环创建这个循环.

Rust不喜欢周期.真的真的不是.Cycles in data structures are almost impossible to create and manage,这种周期也不例外.您试图聪明地使用合成树而不是图形来避免循环,但循环是不可避免的.您避免了数据中的循环,但在代码流中创建了循环.

那么,所有希望都破灭了吗?

一点也不.我们只需要聪明一点.

解决方案是invert the composition.

您试图将LowLevelObject包含在HighLevelObject中.从概念上来说,这是有道理的:毕竟,组合(例如继承)采用现有类型并为其添加功能,所以将"较小"类型包括在"较大"类型中是有道理的,不是吗?

是的但有时世界迫使我们违背逻辑.

当我们将LowLevelObject存储在HighLevelObject中时,我们必须将回调存储在LowLevelObject中.我们无法将它们存储在HighLevelObject中,因此LowLevelObject将无法找到它们.但回调需要对HighLevelObject的引用,而LowLevelObject本身无法给他们这个,HighLevelObject也无法帮助它,因为它会违反别名规则.

换句话说,the callbacks must be stored in the top-level struct.这是因为他们需要访问HighLevelObject(直接)和LowLevelObject(间接,通过其方法).唯一可以为他们提供这种功能的类型是同时拥有两者的类型.但另一方面,回调不能存储在HighLevelObject中,因为LowLevelObject需要调用它们.这些要求的明显解决方案是让LowLevelObject包含HighLevelObject.

你问,这不会 destruct 封装吗?这不会,因为我们将删除HighLevelObject的类型- via generics或动态调度.

Here's how it will look like:

// crate `low`

pub struct LowLevelObject<T> {
    pub listener: T,
    callback1: fn(&mut Self),
    callback2: fn(&mut Self),

    example_low_level_data: String,
}

impl<T> LowLevelObject<T> {
    pub fn low_api1(&mut self) {
        // some functionality
        (self.callback1)(self);
        // more functionality
    }

    pub fn low_api2(&mut self) {
        // some functionality
        (self.callback2)(self);
        // more functionality
    }

    pub fn low_api3(&mut self) {
        // some functionality
        println!("`low_api3()` - {}", self.example_low_level_data);
    }

    pub fn create(listener: T, callback1: fn(&mut Self), callback2: fn(&mut Self)) -> Self {
        Self {
            listener,
            callback1,
            callback2,
            example_low_level_data: "this is low-level data".to_owned(),
        }
    }
}

// crate `high`

pub struct HighLevelState {
    example_high_level_data: String,
}

pub type HighLevelObject = LowLevelObject<HighLevelState>;

// Define them either as free functions or in extension traits, whatever makes you feel better.

fn on_callback1(hi: &mut HighLevelObject) {
    println!("`on_callback1()` - {}", hi.listener.example_high_level_data);
    hi.low_api3();
}

fn on_callback2(hi: &mut HighLevelObject) {
    println!("`on_callback2()` - {}", hi.listener.example_high_level_data);
    hi.low_api3();
}

pub fn high_create() -> HighLevelObject {
    LowLevelObject::create(
        HighLevelState {
            example_high_level_data: "this is high-level data".to_owned(),
        },
        on_callback1,
        on_callback2,
    )
}

这是您的原始代码:

//////////////////////////////////////////////////////////
// Platform specific OSAPP library crate
// e.g. osapp_win.rs

pub struct OSApp<App> {
    pub app: App,
    abnormal_termination: bool,
    with_run_loop: bool,
}

pub fn init_imp<App>(app: App, with_run_loop: bool) -> OSApp<App> {
    OSApp {
        app,
        abnormal_termination: false,
        with_run_loop,
    }
}

pub fn run<App>(app: &mut OSApp<App>, on_finish_launching: &mut dyn FnMut(&mut OSApp<App>)) {
    on_finish_launching(app);

    if app.with_run_loop {
        // Following line commented out to simplify
        // osgui::message_loop();
        i_terminate(app);
    }
}

fn i_terminate<App>(_app: &OSApp<App>) {
    // Calls more client callbacks
}

//////////////////////////////////////////////////////////
// OSAPP crate

use core::f64;
use std::{cell::RefCell, rc::Rc};

struct AppState {
    _lframe: f64,
    func_create: FnAppCreate,
}

type App = OSApp<AppState>;

pub trait ClientObject {}
type FnAppCreate = fn() -> Box<dyn ClientObject>;

pub fn osmain(lframe: f64, func_create: FnAppCreate) {
    let mut app = init_imp(
        AppState {
            _lframe: lframe,
            func_create: func_create,
        },
        true,
    );

    let mut on_finish_launch = |app: &mut App| i_OnFinishLaunching(app);

    /*osapp::*/
    run(&mut app, &mut on_finish_launch);
}

fn i_OnFinishLaunching(app: &App) {
    (app.app.func_create)();
}

//////////////////////////////////////////////////////////
// main.rs

struct Application {
    // widgets go here
}

impl ClientObject for Application {}

impl Application {
    fn create() -> Box<dyn ClientObject> {
        let mut app = Box::new(Application {
            // Create all the widgets here
        });

        app
    }
}

fn main() {
    /*osapp::*/
    osmain(0.0, Application::create);
}

Side note:如果您想在回调中存储捕获关闭,您将面临一些问题.它们可以通过一些技巧修复,但我认为您不需要它们,因为您正在移植C库,而C没有关闭:)

Rust相关问答推荐

有条件默认实现

Tauri tauri—apps/plugin—store + zustand

将JSON密钥转换为Polars DataFrame

在特征中使用Async时,如何解决不透明类型`impl Future<;out=self>;`不满足其关联的类型边界和警告?

如何在Rust中表示仅具有特定大小的数组

如何编写一个以一个闭包为参数的函数,该函数以另一个闭包为参数?

如何高效地将 struct 向量中的字段收集到单独的数组中

try 创建随机数以常量

要求类型参数有特定的大小?

为什么 Rust 需要可变引用的显式生命周期而不是常规引用?

如何从borrow 的异步代码运行阻塞代码?

Rust中如何实现一个与Sized相反的负特性(Unsized)

Rust 打包在 .deb 中

如何在 Rust 中将枚举变体转换为 u8?

使用 HashMap 条目时如何避免字符串键的短暂克隆?

字符串切片的向量超出范围但原始字符串仍然存在,为什么判断器说有错误?

为什么基于 clap::Parser 读取的大量数字进行计算比硬编码该数字时慢?

tokio async rust 的 yield 是什么意思?

有没有办法使用 NASM 语法进行内联汇编?

为什么这里需要类型注解?