《精通Rust》里介紹了 GTK+框架的開發(fā),這篇博客記錄并擴(kuò)展一下。rust 可以用于桌面應(yīng)用開發(fā),我還挺驚訝的,大學(xué)的時(shí)候也有學(xué)習(xí)過 VC++,對(duì)桌面編程一直都很感興趣,而且一直有一種妄念,總覺得自己能開發(fā)一款很好用的桌面程序,就和總覺得自己能彩票中大獎(jiǎng)一樣。
環(huán)境安裝
可能你會(huì)需要安裝 gtk+3。如果執(zhí)行 cargo build 的時(shí)候提示你找不到 gdk-3.0
,那你就需要手動(dòng)安裝一下:
不過,也不需要提前安裝這些依賴。當(dāng)我們執(zhí)行 cargo build 編譯的時(shí)候,結(jié)合 rust 的錯(cuò)誤提示進(jìn)行按需安裝是比較穩(wěn)妥的。
功能開發(fā)
在《精通Rust》書中的16章節(jié),書中的 Demo 忽略了一個(gè)非常重要的細(xì)節(jié),就是省略了依賴包的聲明,沒有依賴包的聲明,代碼就缺少了靈魂,編譯都沒有辦法通過。
我在考慮要不要使用 GPT 自動(dòng)生成一下源碼,省的麻煩。自動(dòng)生成代碼還是放到最后吧…可以通過這個(gè)過程來熟悉一下 rust 的函數(shù)。
std::process::exit
這個(gè)函數(shù)并不陌生,使用一個(gè) code 來退出當(dāng)前的進(jìn)程。要想在程序中正常調(diào)用這個(gè)函數(shù),需要導(dǎo)入如下的頭聲明:
use std::process;
gtk::Window
代碼中使用到的組件都來自于 gtk 包,為了方便起見,可以將 gtk 下的聲明全局導(dǎo)入
use gtk::*
std::sync::mpsc::Sender
用來通過 channel 實(shí)現(xiàn)異步通訊的能力,代碼中用來做數(shù)據(jù)通訊,有 send
和對(duì)應(yīng)的 try_recv
的兩個(gè)動(dòng)作。如果我們不引入這個(gè)包,cargo build 還會(huì)給我們另一個(gè)可選建議 glib::Sender,不過這個(gè)函數(shù)的解釋中提到,兩個(gè)方式是類似的。
use std::sync::mpsc::Sender
std::sync::mpsc::Receiver
有消息的發(fā)送,就應(yīng)該有消息的接受
use std::sync::mpsc::Receiver
std::sync::mpsc::channel
函數(shù)用來創(chuàng)建 Sender、Receive,和上面兩個(gè)函數(shù)是一體的,它創(chuàng)建的是一個(gè)異步隊(duì)列。創(chuàng)建同步隊(duì)列需要調(diào)用 std::sync::mpsc::sync_channel方法。
use std::sync::mpsc::channel
mod
rust 在相同的目錄下,不同文件中聲明的結(jié)構(gòu)體是無法相互引用的,需要通過 mod 來解決。mod 主要用來解決項(xiàng)目?jī)?nèi)代碼組織的問題,use mod xxx
會(huì)嘗試去加載當(dāng)前目錄下的 xxx.rs 文件的代碼。
在 main.rs 中的 mod hackernews
就是用來加載 hackernews.rs
中聲明的導(dǎo)出方法或結(jié)構(gòu)體。如果你將這行代碼刪除,程序就會(huì)找不到文件中聲明的 Story 結(jié)構(gòu)體。
use crate::hackernews::Story;
std::sync::Arc
全稱是 Atomically Reference Counted,表示線程安全的引用計(jì)數(shù)器。Arc 表示一個(gè)指針,指向堆空間的 T 值。同時(shí),有一個(gè)附屬的引用計(jì)數(shù)。
use std::sync::Arc;
reqwest::Client
一個(gè)異步的 HTTP 請(qǐng)求客戶端,用來發(fā)送 HTTP 請(qǐng)求。在說明文檔中明確強(qiáng)調(diào):我們不需要使用 Rc 或者 Arc 去包裝這個(gè)類型,內(nèi)部已經(jīng)使用 Arc 包裝過了。
use reqwest::Client;
glib::source::timeout_add
函數(shù)用于固定間隔執(zhí)行閉包函數(shù),示例中的作用是固定間隔嘗試接受消息。我發(fā)現(xiàn),rust 依賴包的做法特別接近 javascript 。
函數(shù)第一個(gè)參數(shù)的類型是 core::time::Duration,這個(gè)時(shí)間概念和 Go 語(yǔ)言相近,不過 rust 表示的是秒+毫秒的單位,構(gòu)造時(shí)間的時(shí)候可以傳遞秒和毫秒兩個(gè)數(shù)值。
use glib::source::timeout_add;
std::ops::ControlFlow
代碼示例中 ControlFlow::Continue(true)
,目前來看這個(gè) Continue 的含義并不明確,感覺不到它的價(jià)值。
use std::ops::ControlFlow
gtk::Box | gtk::prelude::BoxExt
其中,gtk::prelude::BoxExt 屬于 trait 屬性,rust 中的 trait 等同于 go 語(yǔ)言的 interface 類型,也是實(shí)現(xiàn)多態(tài)的手段之一
trait 特性需要明確聲明出來,否則,trait 特性會(huì)被掩藏。所以,下面的兩個(gè)都需要導(dǎo)入:
use gtk::Box;
use gtk::prelude::BoxExt;
std::future::Future
代碼中使用 reqwest
發(fā)送 http 請(qǐng)求時(shí),send 返回體的類型是 Future,屬于 async 處理方式,包含 Future 類型操作的函數(shù)需要有 async 聲明,await 用來等待返回的結(jié)果。
同一個(gè)線程中,當(dāng)包含多個(gè) async 方法時(shí),這些方法之間就可以實(shí)現(xiàn)“異步”執(zhí)行。如果需要將閉包聲明為 async ,只需要使用 async 關(guān)鍵字聲明代碼塊。
文章來源:http://www.zghlxwxcb.cn/news/detail-603270.html
發(fā)生的奇怪問題
std::marker::Send
問題:required for Arc<std::sync::mpsc::Sender<Msg>>
to implement std::marker::Send
只有實(shí)現(xiàn) Send trait 的類型才可以在線程間安全地轉(zhuǎn)移所有權(quán),而這個(gè)類型明顯沒有實(shí)現(xiàn)。文章來源地址http://www.zghlxwxcb.cn/news/detail-603270.html
代碼
impl App {
pub fn new() -> (App, Receiver<Msg>) {
if gtk::init().is_err() {
println!("Failed to init hews window");
process::exit(1);
}
let (tx, rx) = channel();
let window = gtk::Window::new(gtk::WindowType::Toplevel);
let sw = ScrolledWindow::new(None, None);
let stories = gtk::Box::new(gtk::Orientation::Vertical, 20);
let spinner = gtk::Spinner::new();
let header = Header::new(stories.clone(), tx.clone());
stories.pack_start(&spinner, false, false, 2);
sw.add(&stories);
window.add(&sw);
window.set_default_size(600, 350);
window.set_titlebar(&header.header);
window.connect_delete_event(move |_, _| {
main_quit();
Inhibit(false);
});
}
pub fn launch(&self, rx: Receiver<Msg>) {
self.window.show_all();
let client = Arc::new(reqwest::Client::new());
self.fetch_posts(client.clone());
self.run_event_loop(rx, client);
}
fn fetch_posts(&self, client: Arc<Client>) {
self.spinner.start();
self.tx.send(Msg::Loading).unwrap();
let tx_clone = self.tx.clone();
top_stories(client, 10, &tx_clone);
}
fn run_event_loop(&self, rx: Receiver<Msg>, client: Arc<Client>) {
let container = self.stories.clone();
let spinner = self.spinner.clone();
let header = self.header.clone();
let tx_clone = self.tx.clone();
timeout_add(100, move || {
match rx.try_recv() {
Ok(Msg::NewStory(s)) => App::render_story(s, &container),
Ok(Msg::Loading) => header.disable_refresh(),
Ok(Msg::Loaded) => {
spinner.stop();
header.enable_refresh();
}
Ok(Msg::Refresh) => {
spinner.start();
spinner.show();
(&tx_clone).send(Msg::Loading).unwrap();
top_stories(client.clone(), 10, &tx_clone)
}
Err(_) => {}
}
Continue(true)
});
gtk::main();
}
fn render_story(s: Stroy, stories: >k::Box) {
let title_with_score = format!("{} ({})", s.title, s.score);
let label = gtk::Label::new(&*title_with_score);
let story_url = s.url.unwrap_or("N/A".to_string());
let link_label = gtk::Label::new(&*story_url);
let label_markup = format!("<a href=\"{}\">{}</a>", story_url, story_url);
link_label.set_markup(&label_markup);
stories.pack_start(&label, false, false, 2);
stories.pack_start(&link_label, false, false, 2);
stories.show_all();
}
}
到了這里,關(guān)于rust gtk 桌面應(yīng)用 demo的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!