You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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错误大概率消失了。

二、当前字体加载方案的其他潜在问题

除了任务被取消的问题,你的代码还有几个需要优化的地方:

  1. 内存泄漏风险Box::leak(font_data)会把字体字节数据永久留在内存里,直到程序退出,这不是优雅的做法。
  2. 错误处理太粗糙set(noto_sans_regular).ok()直接忽略了设置失败的情况(比如重复调用load_fonts),出问题时很难排查。
  3. 字体加载时机不匹配: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

火山引擎 最新活动