Rust + Iced GUI运行时加载字体遭遇DNS请求错误的问题排查与方案确认
Rust + Iced GUI运行时加载字体遭遇DNS请求错误的问题排查与方案确认
嗨,我来帮你拆解下这个问题——先从你遇到的DNS错误说起,再确认你的运行时字体加载方案到底靠不靠谱~
一、先搞清楚:你的“DNS错误”其实大概率是任务被取消了!
看你给出的错误信息里有JoinError::Cancelled,这才是核心问题,不是真的DNS解析失败。原因出在你的load_fonts函数里:
你手动创建了Tokio Runtime,spawn异步下载任务后,函数直接退出了——这时候Runtime会被销毁,Tokio默认会立刻终止所有未完成的异步任务,导致你的下载请求还没完成就被砍断了,才会抛出看起来像DNS错误的异常。
验证这个猜想很简单:如果把runtime.spawn改成用runtime.block_on等待下载任务完成,你会发现DNS错误大概率消失了。
二、当前字体加载方案的其他潜在问题
除了任务被取消的问题,你的代码还有几个需要优化的地方:
- 内存泄漏风险:
Box::leak(font_data)会把字体字节数据永久留在内存里,直到程序退出,这不是优雅的做法。 - 错误处理太粗糙:
set(noto_sans_regular).ok()直接忽略了设置失败的情况(比如重复调用load_fonts),出问题时很难排查。 - 字体加载时机不匹配:Iced初始化时需要知道字体,但你异步下载的话,界面启动时字体可能还没准备好,这时候会用默认字体,但下载完成后不会自动切换,需要手动触发重新渲染。
三、优化后的可行方案
我给你调整了代码,解决上述问题,同时保证字体可以在运行时安全加载:
1. 重构字体模块:避免泄漏,安全存储字体数据
use once_cell::sync::OnceCell; use iced::Font; use reqwest::Error; use std::sync::Arc; // 用OnceCell存储字体字节数据,Arc保证内存安全且无泄漏 pub static NOTO_SANS_REGULAR_BYTES: OnceCell<Arc<[u8]>> = OnceCell::new(); pub static NOTO_SANS_BOLD_BYTES: OnceCell<Arc<[u8]>> = OnceCell::new(); // 对外提供获取Font的方法,自动 fallback 到默认字体 pub fn noto_sans_regular() -> Font { NOTO_SANS_REGULAR_BYTES .get() .map(|bytes| Font::External { name: "noto-sans-regular", bytes: bytes, }) .unwrap_or(Font::Default) } pub fn noto_sans_bold() -> Font { NOTO_SANS_BOLD_BYTES .get() .map(|bytes| Font::External { name: "noto-sans-bold", bytes: bytes, }) .unwrap_or(Font::Default) } // 封装下载逻辑,返回Arc<[u8]>保证'static生命周期 async fn download_font(url: &str) -> Result<Arc<[u8]>, Error> { let response = reqwest::get(url).await?; let bytes = response.bytes().await?; Ok(bytes.into()) } // 并行下载字体,添加错误日志 pub async fn load_fonts() { let (regular_result, bold_result) = tokio::join!( download_font("https://raw.githubusercontent.com/googlefonts/noto-fonts/main/unhinted/ttf/NotoSans/NotoSans-Regular.ttf"), download_font("https://raw.githubusercontent.com/googlefonts/noto-fonts/main/unhinted/ttf/NotoSans/NotoSans-Bold.ttf") ); if let Ok(regular_bytes) = regular_result { if let Err(e) = NOTO_SANS_REGULAR_BYTES.set(regular_bytes) { eprintln!("Failed to set regular font: {:?}", e); } } else { eprintln!("Failed to download regular font: {:?}", regular_result.err()); } if let Ok(bold_bytes) = bold_result { if let Err(e) = NOTO_SANS_BOLD_BYTES.set(bold_bytes) { eprintln!("Failed to set bold font: {:?}", e); } } else { eprintln!("Failed to download bold font: {:?}", bold_result.err()); } }
2. 主程序中正确管理Runtime,触发界面更新
#[tokio::main] async fn main() -> iced::Result { // 后台启动字体下载任务,不阻塞界面初始化 tokio::spawn(async { fonts::load_fonts().await; // 这里可以发送一个自定义Message给Iced应用,通知字体加载完成 // 比如你的App有Message::FontLoaded,就用sender发送它,触发重新渲染 // app_sender.send(Message::FontLoaded).ok(); }); // 初始化Iced应用,先注册默认字体,下载完成后自动切换 iced::application("My Rust App", MyApp::update, MyApp::view) .font(fonts::noto_sans_regular()) .font(fonts::noto_sans_bold()) .run() }
四、最后说回“DNS错误”
如果按照上面的方案修改后,还是遇到真实的DNS解析问题,那你需要排查:
- 本地网络是否能正常访问目标域名(可以用
ping raw.githubusercontent.com测试) - 是否有防火墙/代理拦截了请求
- 本地DNS服务器设置是否正常
但根据你的错误日志,99%的概率是之前的任务被取消导致的假DNS错误,修改Runtime管理方式后就能解决。
备注:内容来源于stack exchange,提问作者4r7if3x




