跳到主要内容

72 篇博文 含有标签「Rust」

查看所有标签

2024年标志着Burn架构的重大演变。 传统的深度学习框架常常要求开发者在性能、可移植性和灵活性之间做出妥协;而我们的目标是超越这些权衡。 展望2025年,我们致力于将这一理念应用于整个计算栈,从嵌入式设备到数据中心,涵盖所有领域。

2024年回顾:突破硬件限制

重新定义内核开发

今年之初,我们面临一个限制:我们的WGPU后端依赖于基础的WGSL模板,限制了我们的适应能力。这个挑战促使我们创建了CubeCL [1],这是我们统一内核开发的解决方案。这项任务非常复杂——设计一个抽象层,适用于各种不同的硬件,同时保持顶级性能。我们的结果证明了这一策略的有效性,在大多数基准测试中,性能现在已匹配甚至超过LibTorch。

多后端架构

后端生态系统现已包括CUDA [2]、HIP/ROCm [3]以及支持WebGPU和Vulkan的先进WGPU实现 [4]。迄今为止最显著的成就是在相同硬件上实现不同后端的性能平衡。例如,无论是在CUDA还是Vulkan上执行矩阵乘法操作,性能几乎相同,这直接反映了我们平台无关优化的策略。

我们还引入了新的Router和HTTP后端:Router后端支持多后端的动态混合,而HTTP后端则支持跨多台机器的分布式处理。为了解决内存管理挑战,我们实施了池化和检查点机制,即使在反向传播期间也能实现操作融合。

硬件无关加速

我们的硬件加速策略标志着一个重要的技术里程碑。我们并不依赖于特定平台的库,如cuBLAS [5]或rocBLAS [6],而是开发了一套编译器栈,利用每个平台的最佳特性,同时确保跨平台的兼容性。这涉及克服代码生成和优化中的复杂挑战,尤其是对于矩阵乘法等操作,必须高效利用各种硬件架构的张量核心。

2025年路线图:拥抱极端

在2025年,我们将解决深度学习部署中的两个基本挑战。

小规模:量化

量化对于资源有限的计算至关重要。 我们的方法使用复杂操作的融合,通过“读取时融合”功能,实现如归约等任务在计算管道中的无缝集成。 这种融合策略自动处理操作的打包和解包,确保量化操作高效运行,无需手动调整。 结果是什么?高性能的量化操作在保持精度的同时,降低了资源需求。

大规模:可扩展的分布式计算

在另一端,是分布式计算。 通过利用我们的Router和HTTP后端构建强大的分布式训练基础设施,我们旨在创建一个流畅的分布式计算体验, 使工作负载能够在不同硬件和后端配置之间轻松流动,同时优化异构计算环境中的资源利用。

为了支持这种普遍兼容性的愿景,我们正在扩展我们的后端生态系统,包括:

  • 开发Metal后端,充分利用Apple Silicon的能力,超越当前WGPU的功能;
  • 在Rust中实现一个即时向量化的CPU后端,以增强CPU性能;
  • 开启新的后端可能性,如FPGA支持,确保Burn能够适应任何计算环境。

我们还将大量投资于开发者体验,提供全面的CubeCL文档,并推动Burn API的稳定化。这些改进将使开发者更容易利用我们跨平台能力的全部潜力。

在2024年,我们证明了跨平台性能不需要妥协。 展望2025年,我们将这一原则扩展到整个计算领域——从微控制器到服务器农场。 通过解决两个极端的技术挑战,我们致力于使深度学习在任何规模或硬件限制下都更加高效和易用。

参考文献

鱼雪

本文将深入探讨 Rust 语言中的 vec::Drain 及其 Drop 实现,作为所有权如何防止内存及其他微妙错误的一个例子。

目标读者:能够阅读 Rust 代码,并对其所有权语义及 Drop 特性有基本(且仅是基本)理解的开发者。

引言

在读《The Rust Programming Language》书籍时,偶然发现了 Vec::drain 方法。 常见的编程语言中,没有见过以 drain 命名的方法,这引起了我的好奇心。

什么是 Vec::drain

如果你不熟悉 Vec::drain,可以像下面这样使用它从 Vec抽取元素(类似的方法还存在于 StringHashMap 及其他多种集合类型中):

let mut values = vec![1, 2, 3, 4, 5];
for val in values.drain(1..3) {
println!("Removed: {}", val);
}
println!("Remaining: {:?}", values);

这段代码的输出为:

Removed: 2
Removed: 3
Remaining: [1, 4, 5]

文档描述

截至 Rust 1.83,文档对 Vec::drain 的描述如下(加粗部分为重点):

批量移除向量中指定范围的元素,返回所有被移除元素的迭代器。如果在完全消费之前迭代器被丢弃,它会丢弃剩余被移除的元素。

返回的迭代器保持对向量的可变借用,以优化其实现。

最后一句,特别是我加粗的部分,引起了我的注意,并促使我深入研究其实现。

Vec::drain 的内部实现

一种合理的实现方式

一种完全合理的实现方法是:接收 Vec,复制出所有要移除的元素并放入新的 Vec,更新原始 Vec 以移除这些元素,并返回由新分配的 Vec 支持的迭代器。

然而,这种方法对于计算机来说可能需要大量的前期工作。 如果 Vec 很大(包含数千或数百万个元素)且操作的是中间的一部分, 这将导致大量额外的内存分配和复制操作,甚至在确定是否使用这些值之前。

Rust 的独特实现

因此,Rust 在这里采取了完全不同的方法:它保留了对原始 Vec 的可变引用,并仅从原始存储中读取和更新。

这得益于 Rust 的所有权规则:

  • 只要 Vec::drain 返回的迭代器存在,其他任何地方都无法对原始 Vec 进行读写访问
  • 因此无法通过使迭代器或其支持的存储失效来使其处于错误状态(例如,改变 Vec 中的值,改变其长度等)

Rust 通过创建一个新的数据结构 Drain 来实现这一点,该结构合理地命名,并持有对原始 Vec 的可变引用以及一个用于访问 Vec 值的迭代器。 使用 Drain 上的迭代器方法时,它会转发到对切片的迭代器。这意味着它不需要自己实现迭代,而是可以使用与对切片迭代相同的(经过优化的)实现。 唯一的区别(也是关键的区别)是 drain 通过 unsafe std::ptr::read 调用立即从切片中返回值。

内存安全性保障

如果在 Drain 迭代器访问期间或之后有人能访问 Vec 中的值,或者 Vec 记住所有 Drain 访问的元素,这将是不安全的。

然而,如前所述,只要 Drain 迭代器存在,就无法访问 Vec,因为它通过可变引用持有自身

因此,对于熟悉 Rust 的开发者来说,这部分内容应该相当直接,std::ptr::read 是唯一不寻常的部分。

Drain 的 Drop 实现

Drop 实现的重要性

当迭代器被丢弃——无论是因为在 for 循环中遍历完毕,还是因为在迭代部分元素后手动丢弃——Drain 类型的 Drop 实现会接管。 这意味着 impl Drop for Drain 负责确保 Drain 的不安全清理保持合理,同时避免内存泄漏, 即 Drain 创建时 Vec 遗忘的内存(稍后会详细讨论)。

常见模式与安全性

这是 Rust 中一种常见且值得理解的模式,同时也非常巧妙。 我们将一步步详细讲解——包括所有实现中的代码(截至 Rust 1.85 nightly 版本)。 你可能需要将这些代码与本文并排查看,以便更好地理解上下文!

Drop Trait 的实现

impl<T> Drop for Drain<'_, T> {
fn drop(&mut self) {
// ...
}
}

需要注意的是,drop 接受 &mut self。这意味着我们无法做任何需要拥有 self 所有权的操作,这进一步引出了下一个内容。

DropGuard 结构体

/// 将未被 `Drain` 移除的元素移动回来,以恢复原始的 `Vec`。
struct DropGuard<'r, 'a, T>(&'r mut Drain<'a, T>);

这是一个内部数据结构,仅在此函数体内可用。其目的是确保在正确的位置将所有内容移回原始 Vec

impl<'r, 'a, T> Drop for DropGuard<'r, 'a, T> {
fn drop(&mut self) {
// 实现细节(稍后详细讨论)
}
}

内存操作与安全性

DropGuardDrop 实现确保即使在发生恐慌(panic)的情况下,也能正确地将元素移回原始位置,保持内存安全。

处理 Drain 的构造

Drain 被构造时,Vec::drain 方法通过以下代码行确保即使 Drain 被泄漏,也不会破坏安全性:

// set self.vec length's to start, to be safe in case Drain is leaked
self.set_len(start);

这实际上是截断 Vec,确保它不包含被 drain 移除的范围内的元素。 即使有人调用 std::mem::forget 来泄漏 Drain,也不会破坏安全性,只会导致内存泄漏,这是可控的。

DropGuard 的工作机制

当迭代器被丢弃时,DropGuard 确保所有未被消耗的元素被正确地丢弃,同时恢复原始 Vec 的长度。

这通过以下代码实现:

let iter = mem::take(&mut self.iter);
let drop_len = iter.len();

if drop_len == 0 {
return;
}

let _guard = DropGuard(self);

DropGuard 的存在确保即使在 drop_in_place 调用时发生恐慌,也能保持内存和 Vec 的有效性。

性能与安全性的平衡

Rust 的实现通过仅在必要时移动元素,最大限度地减少了计算和内存开销。 这对于处理大型 Vec 特别重要,因为它避免了不必要的内存分配和复制操作。

结论

通过深入了解 Vec::drain 及其 Drop 实现,我们可以看到 Rust 如何利用所有权语义来提供高性能和内存安全的保障。

具体来说:

  • 原始的 Vec 在使用 drain 期间及之后永远不会处于无效状态
  • Vec 的迭代器永远不会被无效化
  • 即使在面对不良行为的实现(只要没有不安全代码),上述两点依然成立
  • 最坏的情况是内存泄漏,但这依然是安全的

此外,DropGuard 的使用展示了 Rust 如何通过所有权和析构函数自动管理资源,而无需依赖特殊的语言结构。

参考资料

鱼雪

在现代桌面应用开发中,Tauri 框架凭借其轻量级、高性能和安全性,成为开发者们构建跨平台应用的首选工具之一。Tauri 允许开发者使用现代前端框架(如 React、Vue 或 Svelte)构建用户界面,同时利用 Rust 语言处理高效且安全的后端逻辑。然而,前后端之间的高效通信是构建功能丰富且稳定的 Tauri 应用的关键。本文将详细介绍 Tauri 中前后端通信的主要方式——Commands(命令)Events(事件)Channels(通道),并通过示例代码帮助您更好地理解和应用这些技术。

目录

  1. Tauri 简介
  2. 前后端通信方式概述
  3. Commands、Events 与 Channels 的对比
  4. 示例代码
  5. 权限配置示例
  6. 错误处理
  7. 最佳实践与安全性
  8. 性能优化
  9. 实际案例
  10. 总结
  11. 参考资源

Tauri 简介

Tauri 是一个用于构建跨平台桌面应用的框架,支持 WindowsmacOSLinux。它利用前端技术(如 HTML、CSS、JavaScript)构建用户界面,并使用 Rust 处理后端逻辑。与 Electron 相比,Tauri 生成的应用体积更小,性能更优,且具备更高的安全性。

前后端通信方式概述

在 Tauri 框架中,前端(通常使用 JavaScript 框架如 React、Vue 或 Svelte)与后端(Rust 编写)之间的通信是实现应用功能的核心。Tauri 提供了多种通信机制,主要包括 Commands(命令)Events(事件)Channels(通道)。除此之外,还有一些其他的通信方式,如在 Rust 中执行 JavaScript 代码。以下将详细介绍这些通信方式、它们的区别及适用场景。

Commands(命令)

Commands 是前端调用后端 Rust 函数的主要方式。通过命令,前端可以请求后端执行特定任务并获取结果。这种通信方式类似于前端发起的远程过程调用(RPC)。

使用场景

  • 执行复杂逻辑��需要后端处理的数据计算、文件操作、数据库交互等。
  • 获取后端数据:例如,从数据库获取数据并在前端展示。
  • 安全性需求:通过命令调用,能够在 Tauri 的安全模型下细粒度地控制权限。

实现步骤

  1. 在 Rust 后端定义命令

    使用 #[tauri::command] 宏定义一个可供前端调用的函数。

    src-tauri/src/lib.rs
    #[tauri::command]
    fn my_custom_command() {
    println!("我被 JavaScript 调用了!");
    }

    fn main() {
    tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![my_custom_command])
    .run(tauri::generate_context!())
    .expect("运行 Tauri 应用时出错");
    }

    :::note命令名称必须唯一。:::

    :::note由于胶水代码生成的限制,在 lib.rs 文件中定义的命令不能标记为 pub。如果将其标记为公共函数,您将看到如下错误:

    error[E0255]: the name `__cmd__command_name` is defined multiple times
    --> src/lib.rs:28:8
    |
    27 | #[tauri::command]
    | ----------------- previous definition of the macro `__cmd__command_name` here
    28 | pub fn x() {}
    | ^ `__cmd__command_name` reimported here
    |
    = note: `__cmd__command_name` must be defined only once in the macro namespace of this module

    :::

  2. 在前端调用命令

    使用 @tauri-apps/api 提供的 invoke 方法调用后端命令。

    前端 JavaScript 代码示例(如 React 组件)
    import { invoke } from '@tauri-apps/api/core';

    async function greetUser() {
    try {
    const greeting = await invoke('my_custom_command');
    console.log(greeting); // 输出: "我被 JavaScript 调用了!"
    } catch (error) {
    console.error('调用命令时出错:', error);
    }
    }

    // 在适当的生命周期钩子中调用 greetUser
  3. 配置权限

    tauri.conf.json 中,通过 CapabilitiesPermissions 配置命令的访问权限,确保命令的安全调用。

    tauri.conf.json 示例
    {
    "$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/tooling/cli/schema.json",
    "package": {
    "productName": "Pomodoro Timer",
    "version": "0.1.0"
    },
    "tauri": {
    "windows": [
    {
    "label": "main",
    "title": "ToDo Pomodoro",
    "width": 800,
    "height": 600,
    "resizable": true,
    "fullscreen": false,
    "decorations": true,
    "transparent": false,
    "alwaysOnTop": false,
    "visible": true,
    "url": "http://localhost:3000",
    "webviewAttributes": {
    "webPreferences": {
    "nodeIntegration": false
    }
    }
    }
    ],
    "security": {
    "capabilities": [
    {
    "identifier": "greet-capability",
    "description": "Allows the main window to greet users.",
    "windows": ["main"],
    "permissions": ["core:default"]
    }
    ]
    },
    "bundle": {
    "active": true,
    "targets": "all",
    "identifier": "com.yuxuetr.pomodoro",
    "icon": [
    "icons/32x32.png",
    "icons/128x128.png",
    "icons/128x128@2x.png",
    "icons/icon.icns",
    "icons/icon.ico"
    ]
    }
    }
    }

Events(事件)

Events 是 Tauri 中实现后端向前端推送消息的机制。与 Commands 不同,Events 是单向的,适用于需要实时通知前端的场景。

使用场景

  • 状态更新通知:后端状态变化时通知前端
  • 长时间任务进度:报告后台任务的执行进度
  • 系统事件通知:如系统状态变化、文件变动等

实现步骤

  1. 在 Rust 后端发送事件

    src-tauri/src/lib.rs
    use tauri::Manager;

    #[tauri::command]
    async fn start_process(window: tauri::Window) {
    // 模拟一个耗时操作
    for i in 0..100 {
    window.emit("process-progress", i).unwrap();
    std::thread::sleep(std::time::Duration::from_millis(100));
    }
    }
  2. 在前端监听事件

    import { listen } from '@tauri-apps/api/event';

    // 监听进度事件
    await listen('process-progress', (event) => {
    console.log('Progress:', event.payload);
    });

Channels(通道)

Channels 提供了一种双向的、持久的通信通道,特别适合需要持续数据交换的场景。

使用场景

  • 流式数据传输:如实时日志、数据流
  • 长连接通信:需要保持持续通信的场景
  • 复杂的双向数据交换:需要前后端频繁交互的功能

实现示例

  1. 在 Rust 后端创建通道

    src-tauri/src/lib.rs
    use tauri::plugin::{Builder, TauriPlugin};
    use tauri::{Runtime, State, Window};
    use std::collections::HashMap;
    use std::sync::{Mutex, mpsc};

    #[derive(Default)]
    struct ChannelState(Mutex<HashMap<String, mpsc::Sender<String>>>);

    #[tauri::command]
    async fn create_channel(
    channel_id: String,
    state: State<'_, ChannelState>,
    window: Window,
    ) -> Result<(), String> {
    let (tx, mut rx) = mpsc::channel(32);
    state.0.lock().unwrap().insert(channel_id.clone(), tx);

    tauri::async_runtime::spawn(async move {
    while let Some(message) = rx.recv().await {
    window
    .emit(&format!("channel:{}", channel_id), message)
    .unwrap();
    }
    });

    Ok(())
    }
  2. 在前端使用通道

    import { listen } from '@tauri-apps/api/event';
    import { invoke } from '@tauri-apps/api';

    // 创建并使用通道
    async function setupChannel() {
    const channelId = 'my-channel';
    await invoke('create_channel', { channelId });

    await listen(`channel:${channelId}`, (event) => {
    console.log('Received:', event.payload);
    });
    }

在 Rust 中执行 JavaScript

Tauri 还支持从 Rust 后端直接执行 JavaScript 代码,这提供了另一种前后端交互的方式。

实现示例

src-tauri/src/lib.rs
#[tauri::command]
async fn execute_js(window: tauri::Window) -> Result<String, String> {
// 执行 JavaScript 代码
window
.eval("console.log('从 Rust 执行的 JavaScript')")
.map_err(|e| e.to_string())?;

// 执行带返回值的 JavaScript
let result = window
.eval("(() => { return 'Hello from JS'; })()")
.map_err(|e| e.to_string())?;

Ok(result)
}

Commands、Events 与 Channels 的对比

特性Commands(命令)Events(事件)Channels(通道)
调用方向前端 → 后��后端 → 前端双向
响应类型同步/异步异步异步
使用场景一次性请求响应状态通知持续数据交换
数据流单次请求单次响应单向推送双向持续
适用性通用操作状态更新流式传输

示例代码

Commands 示例

完整的文件操作示例:

src-tauri/src/main.rs
use std::fs;
use tauri::command;

#[command]
async fn read_file(path: String) -> Result<String, String> {
fs::read_to_string(path)
.map_err(|e| e.to_string())
}

#[command]
async fn write_file(path: String, contents: String) -> Result<(), String> {
fs::write(path, contents)
.map_err(|e| e.to_string())
}

fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![read_file, write_file])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
// 前端调用示例
import { invoke } from '@tauri-apps/api/tauri';

async function handleFileOperations() {
try {
// 写入文件
await invoke('write_file', {
path: 'test.txt',
contents: 'Hello, Tauri!',
});

// 读取文件
const content = await invoke('read_file', {
path: 'test.txt',
});
console.log('File content:', content);
} catch (error) {
console.error('File operation failed:', error);
}
}

Events 示例

文件监控示例:

src-tauri/src/main.rs
use notify::{Watcher, RecursiveMode, watcher};
use tauri::Manager;
use std::time::Duration;

#[tauri::command]
async fn watch_directory(window: tauri::Window, path: String) -> Result<(), String> {
let (tx, rx) = std::sync::mpsc::channel();

let mut watcher = watcher(tx, Duration::from_secs(2)).map_err(|e| e.to_string())?;

watcher.watch(&path, RecursiveMode::Recursive).map_err(|e| e.to_string())?;

tauri::async_runtime::spawn(async move {
for res in rx {
match res {
Ok(event) => {
window.emit("file-change", event).unwrap();
}
Err(e) => println!("watch error: {:?}", e),
}
}
});

Ok(())
}
// 前端监听示例
import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/tauri';

async function setupFileWatcher() {
// 启动文件监控
await invoke('watch_directory', {
path: './watched_folder',
});

// 监听文件变化事件
await listen('file-change', (event) => {
console.log('File changed:', event.payload);
});
}

Channels 示例

实时日志流示例:

src-tauri/src/main.rs
use tokio::sync::mpsc;
use std::collections::HashMap;
use std::sync::Mutex;

struct LogChannel(Mutex<HashMap<String, mpsc::Sender<String>>>);

#[tauri::command]
async fn start_log_stream(
channel_id: String,
state: tauri::State<'_, LogChannel>,
window: tauri::Window,
) -> Result<(), String> {
let (tx, mut rx) = mpsc::channel(100);
state.0.lock().unwrap().insert(channel_id.clone(), tx);

tauri::async_runtime::spawn(async move {
while let Some(log) = rx.recv().await {
window
.emit(&format!("log:{}", channel_id), log)
.unwrap();
}
});

Ok(())
}
// 前端实现
import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/tauri';

async function setupLogStream() {
const channelId = 'app-logs';

// 创建日志流通道
await invoke('start_log_stream', { channelId });

// 监听日志消息
await listen(`log:${channelId}`, (event) => {
console.log('New log:', event.payload);
});
}

权限配置示例

{
"tauri": {
"security": {
"capabilities": [
{
"identifier": "file-access",
"description": "允许读写文件",
"windows": ["main"],
"permissions": ["fs:default"]
},
{
"identifier": "log-stream",
"description": "允许访问日志流",
"windows": ["main"],
"permissions": ["event:default"]
}
]
}
}
}

错误处理

src-tauri/src/lib.rs
#[derive(Debug, thiserror::Error)]
enum Error {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid input: {0}")]
InvalidInput(String),
}

impl serde::Serialize for Error {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}

#[tauri::command]
async fn handle_with_error() -> Result<String, Error> {
// 业务逻辑
Ok("Success".to_string())
}
// 前端错误处理
import { invoke } from '@tauri-apps/api/tauri';

try {
await invoke('handle_with_error');
} catch (error) {
console.error('Operation failed:', error);
}

最佳实践与安全性

  1. 权限控制

    • 始终使用最小权限原则
    • 明确定义每个命令的权限需求
    • 使用 allowlist 限制可用 API
  2. 数据验证

    • 在前后端都进行数据验证
    • 使用强类型定义接口
    • 处理所有可能的错误情况
  3. 性能优化

    • 使用适当的通信方式
    • 避免频繁的小数据传输
    • 合理使用异步操作

性能优化

  1. 批量处理

    • 合并多个小请求
    • 使用数据缓存
    • 实现请求队列
  2. 数据压缩

    • 大数据传输时使用压缩
    • 选择适当的序列化格式
  3. 异步处理

    • 使用异步命令
    • 实现后台任务
    • 合理使用线程池

实际案例

  1. 文件管理器

    • 使用 Commands 处理文件操作
    • 使用 Events 监控文件变化
    • 使用 Channels 传输大文件
  2. 实时聊天应用

    • 使用 Channels 处理消息流
    • 使用 Events 处理状态更新
    • 使用 Commands 处理用户操作

总结

Tauri 提供了丰富的前后端通信机制,每种方式都有其特定的使用场景:

  • Commands 适合一次性的请求-响应模式
  • Events 适合单向的状态通知
  • Channels 适合持续的双向数据交换

选择合适的通信方式对应用的性能和用户体验至关重要。同时,始终要注意安全性,合理使用权限控制和数据验证。

参考资源

鱼雪

Rust 提供了强大的类型系统,但在可迭代特性上仍有一些可改进之处。

本文将讨论 CollectionIterable 特性的定义与实现,以及它们在提升代码通用性与可复用性上的作用。


背景

在 Rust 的核心库中,我们已经有了 IteratorIntoIterator 特性。 然而,对于可以多次迭代的集合类型(Collection)或通用的可迭代类型(Iterable),目前还缺乏统一的特性支持。 我们将深入探讨这些特性的重要性和实现方式。


核心概念

Iterator

Iterator 特性用于逐步遍历集合中的元素或计算结果。它是一种懒加载模型,仅在调用消费方法时才执行计算。

常见的 Iterator 方法包括:

  • 迭代器到迭代器的转换filtermapflat_map
  • 迭代器的消费方法collectreducefold
let numbers = vec![1, 2, 3];
let doubled: Vec<_> = numbers.iter().map(|x| x * 2).collect();
println!("{:?}", doubled); // [2, 4, 6]

IntoIterator

IntoIterator 特性允许将类型转换为迭代器:

pub trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;

fn into_iter(self) -> Self::IntoIter;
}

它支持三种实现方式:

  1. 消费自身:x.into_iter()
  2. 消费不可变引用:(&x).into_iter()
  3. 消费可变引用:(&mut x).into_iter()

定义 Collection 和 Iterable 特性

Collection 和 CollectionMut 特性

Collection 表示可以反复生成共享引用迭代器的集合,CollectionMut 进一步扩展为支持生成可变引用迭代器。

trait Collection {
type Item;
type Iter<'i>: Iterator<Item = &'i Self::Item>
where
Self: 'i;

fn iter(&self) -> Self::Iter<'_>;
}

trait CollectionMut: Collection {
type IterMut<'i>: Iterator<Item = &'i mut Self::Item>
where
Self: 'i;

fn iter_mut(&mut self) -> Self::IterMut<'_>;
}

通过 IntoIterator 的实现,可以简洁地为所有符合条件的集合实现这些特性:

impl<X> Collection for X
where
X: IntoIterator,
for<'a> &'a X: IntoIterator<Item = &'a <X as IntoIterator>::Item>,
{
type Item = <X as IntoIterator>::Item;
type Iter<'i> = <&'i X as IntoIterator>::IntoIter;

fn iter(&self) -> Self::Iter<'_> {
<&X as IntoIterator>::into_iter(self)
}
}

impl<X> CollectionMut for X
where
X: IntoIterator,
for<'a> &'a mut X: IntoIterator<Item = &'a mut <X as IntoIterator>::Item>,
{
type IterMut<'i> = <&'i mut X as IntoIterator>::IntoIter;

fn iter_mut(&mut self) -> Self::IterMut<'_> {
<&mut X as IntoIterator>::into_iter(self)
}
}

Iterable 特性

Iterable 是对更广泛可迭代类型的定义,支持生成值迭代器,而不是引用迭代器。

trait Iterable {
type Item;
type Iter: Iterator<Item = Self::Item>;

fn iter(&self) -> Self::Iter;
}

实现方式如下:

impl<'a, X> Iterable for &'a X
where
&'a X: IntoIterator,
{
type Item = <&'a X as IntoIterator>::Item;
type Iter = <&'a X as IntoIterator>::IntoIter;

fn iter(&self) -> Self::Iter {
self.into_iter()
}
}

示例

Collection 示例

以下示例展示了如何使用 Collection 计算集合的统计信息:

fn statistics(numbers: &impl Collection<Item = i64>) -> Stats {
let count = numbers.iter().count() as i64;
let mean = numbers.iter().sum::<i64>() / count;
let sum_sq_errors: i64 = numbers.iter().map(|x| (x - mean) * (x - mean)).sum();
let std_dev = f64::sqrt(sum_sq_errors as f64 / (count - 1) as f64) as i64;

Stats { count: count as usize, mean, std_dev }
}

支持的集合类型包括:

  • 数组和向量
  • 标准库集合(如 HashSetVecDeque
  • 第三方集合(如 SmallVecArrayVec

CollectionMut 示例

以下示例展示了如何使用 CollectionMut 修改集合中的元素:

fn increment_by_sum(numbers: &mut impl CollectionMut<Item = i32>) {
let sum: i32 = numbers.iter().sum();
for x in numbers.iter_mut() {
*x += sum;
}
}

Iterable 示例

以下示例展示了使用 Iterable 的灵活性,包括对范围和自定义生成器的支持:

fn statistics(numbers: impl Iterable<Item = i64>) -> Stats {
/* 与 Collection 示例相同 */
}

statistics(7..21); // 支持范围
statistics(FibUntil(10)); // 自定义生成器

优势与结论

优势

  • 自动实现:无需额外配置,集合类型可以自动实现 CollectionCollectionMut
  • 广泛适用:支持标准集合、自定义集合和生成器类型。
  • 灵活性:通过 Iterable 扩展了迭代器的应用范围。

总结

通过引入 CollectionIterable 特性,我们显著提升了代码的复用性和通用性。 Rust 的类型系统为实现这些特性提供了极大的便利,再次展现了其强大的能力 ❤️🦀。

链接

鱼雪

2024 年 12 月 9 日,Dioxus 0.6 重磅发布!这次更新带来了许多全新的工具特性,显著提升了开发者体验,包括移动模拟器支持、热重载、交互式 CLI 等,助力开发者更高效地构建全栈应用。


什么是 Dioxus?

Dioxus 是一个全栈开发框架,支持通过单一代码库构建 Web、桌面和移动应用。我们的目标是打造一个比 Flutter 更强大的框架。Dioxus 专注于:

  • 一流的全栈 Web 支持
  • 类型安全的服务器/客户端通信
  • 极致的性能

0.6 版本的主要亮点

此次发布,我们重新设计了 Dioxus CLI,大幅提升了开发者体验,修复了许多长期存在的问题,并引入了一系列新功能。

1. 全新 Dioxus CLI

以下是 Dioxus CLI 的关键改进:

  • dx serve for mobile:支持在 Android 和 iOS 模拟器及设备上运行应用。
  • 神奇的热重载:支持格式化字符串、属性和嵌套的 rsx!{} 的热重载。
  • 交互式 CLI:借鉴 Astro 的交互式用户体验,重新设计了 Dioxus CLI。
  • 内联堆栈跟踪:直接在终端中捕获 WASM 崩溃和日志。
  • 桌面和移动端的服务器函数支持:为本地应用内联服务器 RPC。

2. 全框架开发者体验改进

我们还在框架的其他方面进行了大量优化:

  • 通知与加载屏幕:开发模式下新增通知与加载屏幕,提升调试体验。
  • 自动补全改进:大幅提升 RSX 的自动补全效果。
  • asset! 稳定化:稳定了集成于原生应用的基于链接器的资源系统。
  • 流式 HTML:支持从服务器到客户端的流式 Suspense 和错误边界。
  • 静态网站生成(SSG)与增量静态生成(ISG):支持更多静态网站构建模式。
  • 事件错误处理:在事件处理器、任务和组件中使用 ? 处理错误。
  • Meta 元素:新增 HeadTitleMetaLink 元素,用于设置文档属性。
  • 同步的 prevent_default:跨平台同步处理事件。
  • onresize 事件处理器:无需 IntersectionObserver 也能跟踪元素大小变化。
  • onvisible 事件处理器:无需 IntersectionObserver 也能跟踪元素可见性。
  • WGPU 集成:支持将 Dioxus 渲染为 WGPU 表面及子窗口的覆盖层。
  • dx bundle:全面支持 Web、iOS 和 Android 平台的打包。
  • JSON 模式:CLI 消息支持 JSON 输出,便于第三方工具和 CI/CD 流程使用。
  • 新模板:新增三个跨平台应用的启动模板。
  • 教程与指南:推出面向 Dioxus 0.6 及后续版本的新教程和指南。
  • 二进制补丁原型:基于纯 Rust 的热重载引擎原型。

重点改进详情

神奇的热重载

Dioxus 的热重载功能实现了前所未有的便利性,不仅支持常规的代码热更新,还能对嵌套的 rsx!{} 结构进行即时刷新。这极大提升了开发效率,尤其是在复杂 UI 开发场景中。

交互式 CLI

新版 CLI 借鉴了 Astro 的交互式设计,提供了更直观的用户体验。例如,在构建项目时,CLI 会根据用户选择动态更新配置,减少不必要的手动操作。

WASM 崩溃与日志捕获

通过内联堆栈跟踪功能,开发者可以直接在终端中查看 WASM 的崩溃原因及日志信息。这有助于快速定位问题,尤其是在调试复杂 Web 应用时。


新功能概览

特性描述
移动模拟器支持在 Android 和 iOS 上快速运行和测试应用。
流式 HTML 支持从服务器流式加载 Suspense 和错误边界,提升性能。
SSG 与 ISG 支持更灵活的静态网站生成和增量更新支持。
WGPU 集成在 WGPU 表面和子窗口中渲染 Dioxus 应用。
新教程与模板快速上手跨平台开发的全新模板和详细指南。
JSON 模式CLI 消息支持 JSON 格式,便于与 CI/CD 集成。
事件处理改进新增同步 prevent_defaultonresize 等功能。
二进制补丁原型纯 Rust 实现的热重载引擎,带来更快的开发迭代体验。

未来发展

Dioxus 将继续优化开发体验,增加对更多平台和场景的支持。我们希望通过 Dioxus,开发者能够更高效地构建现代化应用。

鱼雪

在本文中,我将分享如何使用 Rust 宏来解决复杂构建需求,同时探索 macro-by-exampleproc-macro 的实现方式。


背景故事

安德烈·乌涅洛·利赫内罗维茨提出用 Rust 重写 Kubernetes 服务的想法并获得了批准。 这让我在最近写了大量的 Rust 代码。 Rust 的宏系统是其中最复杂的部分之一,即使对于经验丰富的 Rust 开发者也是如此。

安德烈·乌涅洛·利赫内罗维茨的场景是构建一个工具,用来与多个内部服务通信。 这些服务的连接模式各异,例如 Basic Auth、Bearer Tokens 和 OAuth,每种模式都有不同的字段要求。 这显然是一个适合使用 构建者模式 的场景。


现有库的调研

在着手实现之前,我调研了几个现有的库:

  • derive_builder:支持删除 Option 字段,但会将其设为必填,不符合我的需求。
  • builder_macro:保留 Option 字段,但生成的代码不够整洁。

因此,我决定自己编写一个宏来实现自动化。


使用 macro-by-example

首先,我尝试了使用 macro_rules! 来实现自动化。以下是我的改进实现:

macro_rules! builder {
(@builder_field_type Option<$ftype:ty>) => { Option<$ftype> };
(@builder_field_type $ftype:ty) => { Option<$ftype> };
($builder:ident -> $client:ident { $( $fname:ident{$($ftype:tt)+} $(,)? )* }) => {
#[derive(Debug)]
pub struct $client {
$( $fname: $($ftype)+, )*
}

#[derive(Debug)]
pub struct $builder {
$( $fname: $crate::builder!(@builder_field_type $($ftype)+), )*
}

impl $builder {
$(
paste::paste! {
pub fn [<with_ $fname>](&mut self, $fname: $crate::builder!(@builder_field_setter_type $($ftype)+)) -> &mut Self {
self.$fname = Some($fname);
self
}
}
)*

pub fn build(&self) -> Result<$client, std::boxed::Box<dyn std::error::Error>> {
Ok($client {
$( $fname: $crate::builder!(@builder_unwrap_field self $fname $($ftype)+), )*
})
}
}

impl $client {
pub fn builder() -> $builder {
$builder {
$( $fname: None, )*
}
}
}
};
}

builder!(Builder -> Client {
field{bool},
});

最终效果是一个灵活的 Builder 宏,支持动态生成字段和方法。


使用过程宏(proc-macro

过程宏是另一种强大的实现方式,允许我们动态解析 TokenStream 并生成代码。

以下是一个基本的过程宏示例:

use proc_macro::TokenStream;
use quote::quote;

#[proc_macro_derive(Builder)]
pub fn builder_derive(input: TokenStream) -> TokenStream {
let expanded = quote! {
#[derive(Debug)]
pub struct Builder {
}
};
TokenStream::from(expanded)
}

更复杂的版本可以解析结构体的字段,并动态生成相应的 Builder 结构体和方法。

以下代码示例展示了如何提取字段类型并实现自定义构建逻辑:

fn inner_type(ty: &Type) -> (bool, &Type) {
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.first() {
if segment.ident == "Option" {
if let syn::PathArguments::AngleBracketed(ref angle_bracketed) =
segment.arguments
{
if let Some(syn::GenericArgument::Type(ref inner_ty)) =
angle_bracketed.args.first()
{
return (true, inner_ty);
}
}
}
}
}
(false, ty)
}

最终,完整的 proc-macro 实现可以自动生成 Builder 模式的完整逻辑。


比较两种实现方式

特性macro-by-exampleproc-macro
易用性相对简单,适合快速实现需要更多代码和独立的 crate
灵活性受限于宏系统的规则,不能动态生成类型名可动态生成字段、方法和类型
适用场景适合固定结构或简单需求适合复杂的动态代码生成

总结

Rust 的宏系统强大而灵活,无论是 macro-by-example 还是 proc-macro 都各有用武之地。

在解决重复性代码生成时,这些工具可以大幅提升开发效率。希望这篇文章能帮助初学者更好地理解和使用 Rust 宏。

链接

鱼雪

本文旨在比较 Diesel 与其他连接关系型数据库的 Rust 库。

汇总对比表

特性DieselSQLxSeaORMtokio-postgres/mysql/rusqlite
稳定性稳定 (2.0 版发布于 2022 年)不稳定 (0.x 版本)稳定 (1.0 版发布于 2024 年)不稳定 (0.x 版本)
安全性编译时检查,类型系统支持编译时检查,需运行数据库部分语法检查,无类型验证仅语法验证
灵活性支持动态查询和扩展静态查询检查,不支持动态查询DSL 支持有限,扩展性弱支持所有 SQL 查询
可扩展性高,可自定义扩展 DSL/后端低,无法扩展核心功能低,无法扩展 DSL/后端不适用
可用性需 C 库支持 (SQLite)需 C 库支持 (SQLite)易于使用,无需额外依赖易于使用
性能高性能,支持查询流水线性能良好,不支持流水线性能一般,不支持流水线性能一般

详细分析

稳定性

Rust 库通常遵循语义版本控制(SemVer),其稳定性由版本决定。版本号高于 1.0 的库承诺在主版本升级前不做破坏性变更。以下是库的现状:

  • Diesel:1.0 发布于 2018 年,2.0 于 2022 年发布。
  • SeaORM:1.0 发布于 2024 年夏季。
  • 其他库仍处于 0.x 状态。

安全性保证

根据提供的安全级别,可以将这些库分为三类:

  1. 纯数据库接口:接受 SQL 字符串,但语法错误需用户自行处理。
  2. 未验证的查询生成器:提供 DSL,但无法验证类型和约束。
  3. 编译时检查:通过编译时检查验证查询。

Diesel 和 SQLx 的实现差异:

  • Diesel 使用 Rust 类型系统进行检查,支持动态构建查询。
  • SQLx 使用宏检查静态查询,但需要运行数据库实例。

灵活性

各库 API 的灵活性差异:

  • tokio-postgres、rusqlite 和 mysql:接受 SQL 字符串,因此支持所有查询。
  • SeaORM:DSL 支持常见 SQL 功能,但限制了高级查询(如超过 3 个表的联接)。
  • SQLx:宏检查静态查询,不支持基于运行时信息的动态查询。
  • Diesel:DSL 覆盖大部分常见 SQL 功能,并支持自定义扩展。

可扩展性

以下是库的可扩展性差异:

  • SeaORM:通过枚举实现 DSL,不支持自定义扩展。
  • SQLx:依赖宏,无法轻松扩展。
  • Diesel:广泛使用特性(traits),支持自定义 DSL 和后端扩展。

可用性

  • Diesel、Diesel-async、SQLx、SeaORM、tokio-postgres 等提供纯 Rust 实现,易于编译。
  • Diesel 和 rusqlite 的 SQLite 后端依赖 C 库,需预先安装。

性能

各库性能概述:

  1. 异步 SQLite 表现较差:SQLite 缺乏异步 API。
  2. 小数据输出性能相似,大数据输出差异明显:Diesel 在数据反序列化方面表现优异。
  3. 查询流水线:Diesel-async 和 tokio-postgres 支持查询流水线,可提升 PostgreSQL 性能 20%。

关于异步数据库库的必要性

异步库的性能优势主要体现在高网络延迟或需要中断请求的场景。 对于 SQLite,使用同步库更高效。 Diesel-async 可支持异步场景。

链接

鱼雪

概述

Burn 0.15.0 带来了显著的性能改进,特别是在矩阵乘法和卷积操作方面。

此外,此版本还引入了以下重要更新:

  • 实验性支持:新增 ROCm/HIPSPIR-V 支持,通过 CubeCL 运行时实现。
  • 多后端兼容性:奠定多后端支持的基础。
  • 新特性:增加了量化操作支持。
  • ONNX 支持扩展:包括更多的算子支持和错误修复,以提升覆盖率。

除此之外,Burn 0.15.0 还包含多项错误修复、性能优化、新的张量操作,以及改进的文档支持。


模块与张量相关更新

  • 移除:对常量泛型模块的拷贝限制。
  • 新增deform_conv2d(实现于 torchvision)、Softminroundfloorceil 等浮点操作。
  • 增强:为张量同步增加支持,添加 tensor.one_hot 整数操作。
  • 更改:LR 调度器调整为首次调用 .step() 时返回初始学习率。

ONNX 支持扩展

  • 支持多维索引的 gather 操作。
  • 增强张量形状跟踪能力。
  • 新增 ConvTranspose1dtrilu 操作支持。
  • 修复 where 操作在标量输入下的行为。

后端改进

  • 支持 CudaDeviceMetalDevice,避免重复创建设备。
  • 新增 SPIR-V 编译器支持 (burn-wgpu) 和 HIP 支持 (burn-hip)。
  • 引入 BackendRouter,为分布式后端处理铺路。
  • 修复自动微分相关的内存泄漏和 NaN 问题。

文档与示例

  • 新增自定义 cubecl 内核的文档。
  • 改进了回归任务的示例和 burn-tch 文档。
  • 修复了多个 Burn Book 的链接及 Raspberry Pi 示例的编译问题。

性能与优化

  • 性能提升:增强了切片内核的性能,改进了 conv2dconv_transpose2d 的自动调优。
  • 数据局部性优化:为隐式 GEMM 提供更好的性能支持,并新增边界检查以支持任意输入形状。

Miscellaneous 更新

  • 工具链:更新了 CI 工作流及工具,修复编译器设置的多处问题。
  • 兼容性:确保最小支持 Rust 版本为 1.81。

参考


通过 Burn 0.15.0,深度学习开发者可以更高效地利用 GPU 加速和量化技术,同时享受多后端支持带来的灵活性。欢迎尝试新版本并加入我们的社区,共同推动 Rust 生态的技术进步!

鱼雪