实现egui窗体隐藏和重新展示

近期在使用 egui 时遇到一个问题,我想要将窗体隐藏起来,再通过托盘图标唤出,本来以为如此简单的需求应当非常简单,结果我发现 egui 官方的框架 eframe 对这个功能的支持并不好,在 GitHub 上长期有着相关讨论,例如这个Issue

本文中,我将首先复现 eframe 的问题,然后介绍一个通过 egui_glowegui_winit 实现的解决方案,提供一个尚且能用的示例。

复现问题

前置依赖

以下是我的 Cargo.toml 文件:

1
2
3
4
5
6
7
8
9
[package]
name = "test_egui"
version = "0.1.0"
edition = "2024"

[dependencies]
egui = "0.31.1"
eframe = "0.31.1"
tokio = { version = "1.44.1", features = ["full"] }

问题复现

为了简化问题,我将上述问题抽象成这样一个模型:编写一个rust程序,后台线程控制它定时出现或消失

为了实现这个功能,我们很容易就会写出这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct MyApp {}

impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.horizontal(|ui| {
ui.label("Hello, world!");
});
});
}
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
let _guard = rt.enter();

eframe::run_native(
"Egui Test App",
Default::default(),
Box::new(|_cc| {
let ctx = _cc.egui_ctx.clone();
rt.spawn(async move {
loop {
println!("Running background task...");
ctx.send_viewport_cmd(egui::ViewportCommand::Visible(false));
ctx.request_repaint();
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
ctx.send_viewport_cmd(egui::ViewportCommand::Visible(true));
ctx.request_repaint();
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
});
Ok(Box::new(MyApp {}))
}),
)?;

Ok(())
}

然而很遗憾,这个代码并不能正常工作。我们可以看到,窗体在后台线程中是可以被隐藏的,但是调用 ctx.send_viewport_cmd(egui::ViewportCommand::Visible(true)); 却没有任何效果,通过打印出来的日志可以看出,循环是正常执行的,但是窗体并没有重新展示出来。
这是因为 eframe 在窗体隐藏时不会去处理这些事件,从 GitHub 的相关讨论来看,目前这是一个没有被解决的问题,因此,我们需要暂时不使用 eframe,而是使用 egui 的底层库 egui_glowegui_winit 来实现这个功能。

解决方案

如果我们能够自己处理 winit 的事件循环,那么就可以轻松定制有关窗体的行为了,我找到了官方的一个示例:Prue-glow

更新依赖

既然我们不再使用 eframe 了,那么我们需要参照上方链接把 Cargo.toml 中的依赖更新为:

1
2
3
4
5
6
7
8
9
10
11
[dependencies]
egui = "0.31.1"
egui-winit = "0.31.1"
winit = "0.30.9"
glow = "0.16.0"
egui_glow = {version = "0.31.1", features = ["winit"]}
glutin = "0.32.2"
glutin-winit = "0.5.0"

tokio = { version = "1.44.1", features = ["full"] }
log = "0.4.26"

改造后的main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
fn main() -> Result<(), Box<dyn std::error::Error>> {
let event_loop = winit::event_loop::EventLoop::<event::UserEvent>::with_user_event()
.build()
.unwrap();
let proxy = event_loop.create_proxy();

let rt = tokio::runtime::Runtime::new()?;
let _guard = rt.enter();
let proxy_clone = proxy.clone(); // !NOTICE: clone the proxy for the background task

rt.spawn(async move {
loop {
println!("Running background task...");
proxy_clone
.send_event(event::UserEvent::HideWindow)
.unwrap();
proxy_clone
.send_event(event::UserEvent::Redraw(Duration::ZERO))
.unwrap();
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
proxy_clone
.send_event(event::UserEvent::ShowWindow)
.unwrap();
proxy_clone
.send_event(event::UserEvent::Redraw(Duration::ZERO))
.unwrap();
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
});

let mut app = app::GlowApp::new(
proxy,
Box::new(|egui_ctx| {
egui::CentralPanel::default().show(egui_ctx, |ui| {
ui.heading("Hello World!");
});
}),
);
event_loop.run_app(&mut app).expect("failed to run app");

Ok(())
}

这是我改造之后的main 函数,主要的变化在于我们使用 winit 的事件循环来处理窗体的行为。我们在后台线程通过 EventLoopProxy<UserEvent> 中发送 UserEvent 来控制窗体的显示和隐藏。

事件处理

同理,我们需要实现 ApplicationHandler<UserEvent>,这里才是接受并处理 UserEvent 的地方。如果你需要管理应用的一些状态,可以直接在 GlowApp 结构体中添加相关字段(如我注释掉的 AppState),并且在 user_event 中处理与更新,这里我只建议进行简单的数据显示等操作,计算的部分还是放在后台比较好,否则会影响到UI的流畅度。另外,你也可能会需要更新 update_ui 以接受 AppState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
pub struct GlowApp {
proxy: winit::event_loop::EventLoopProxy<UserEvent>,
gl_window: Option<GlutinWindowContext>,
gl: Option<Arc<glow::Context>>,
egui_glow: Option<egui_glow::EguiGlow>,
repaint_delay: std::time::Duration,
clear_color: [f32; 3],
window_hidden: bool,
update_ui: Box<dyn Fn(&egui::Context) + Send + Sync + 'static>,
// state: AppState,
}

impl winit::application::ApplicationHandler<UserEvent> for GlowApp {
// ... skip other methods
fn user_event(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop, event: UserEvent) {
match event {
UserEvent::Redraw(delay) => self.repaint_delay = delay,
UserEvent::ShowWindow => {
self.window_hidden = false;
if let Some(ref gl_window) = self.gl_window {
gl_window.window().set_visible(true);
gl_window.window().request_redraw();
}
}
UserEvent::HideWindow => {
self.window_hidden = true;
if let Some(ref gl_window) = self.gl_window {
gl_window.window().set_visible(false);
}
}
}
}
}

总结

至此,程序窗体就会定时隐藏和重新展示了,若想要实现角标的事件处理功能,可以参考这个示例来把 tray 事件添加到我们的事件中,再在 user_event 中处理。

完整代码

为什么我不在上方直接呈现出所有代码呢?当然是因为太长了……我认为对于实现简单的需求来说,了解上面的部分就可以很快地上手修改了,把我的(或者官方的)案例复制走即可。当然,我这份代码有许多小问题,不适合直接用于生产环境,请仔细检查后再使用。

另外,我也正在尝试构建一个 egui 桌面应用的模版,欢迎关注我的 GitHub 主页:mcthesw,如果成功,我会在这里更新相关信息。

在Win上编译Rust程序到MacOS和Linux

我之前写过一篇文章介绍如何在Windows下编译适用于Linux的Rust程序,不过Rust原生的配置方法并不算简单,今天我想介绍一个更简单的方法来编译Rust程序到MacOS和Linux。同理,也可以在这些平台上编译到Windows。

Cross-rs

Cross-rs是一个Rust的交叉编译工具,在依赖不复杂的情况下,几乎能够一条指令就将Rust程序编译到其他平台。它的原理是使用Docker来运行交叉编译的工具链,因此需要机子上有Docker或Podman。这个库也支持测试,这不是本文章关注的内容,因此只在这提一下。

安装

值得注意的是,由于这个项目依赖Docker,所以在编译时需要保持网络畅通,Docker Hub的镜像下载速度可能会很慢,建议使用你能快速访问的镜像源。

在确保 Rust 工具链、Docker 或 Podman 已经安装的情况下,使用以下命令安装 cross:

1
cargo install cross --git https://github.com/cross-rs/cross

基本用法

对于简单的项目,安装完成后就可以直接使用 cross 来编译了:

1
2
3
4
# 编译到目标架构是x86_64的Linux
cross build --target x86_64-unknown-linux-gnu --release
# 编译到目标架构是aarch64的Linux
cross build --target aarch64-unknown-linux-gnu --release

这里以我以前练手写的一个Socks5代理软件为例,编译结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cross build --target x86_64-unknown-linux-gnu --release

# info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'
# info: latest update on 2025-03-18, rust version 1.85.1 (4eb161250 2025-03-15)
# info: downloading component 'cargo'
# ......
# info: checking for self-update
# info: downloading component 'rust-src'
# info: installing component 'rust-src'

# Unable to find image 'ghcr.io/cross-rs/x86_64-unknown-linux-gnu:main' locally
# main: Pulling from cross-rs/x86_64-unknown-linux-gnu
# d9802f032d67: Pull complete
# ......
# b4f2b8f6bece: Pull complete
# Digest: sha256:e01aa4c146da3834f49ead052f7f9b9cff95e3dd576f909c87cea473bba84e1b
# Status: Downloaded newer image for ghcr.io/cross-rs/x86_64-unknown-linux-gnu:main

# Compiling libc v0.2.159
# ......
# Compiling tokio-byteorder v0.3.0
# Compiling socks5_tcp v0.1.0 (/mnt/d/socks5_tcp)
# Finished `release` profile [optimized] target(s) in 58.03s

可见,基本上分为以下几个阶段:

  1. 安装目标平台的工具链
  2. 下载用于编译的Docker镜像
  3. 编译程序

然后我们就得到了一个在Linux上运行的可执行文件,位于target/x86_64-unknown-linux-gnu/release/socks5_tcp。然而,在面对复杂的项目时,可能会需要处理外部依赖;而编译到MacOS或MSVC目标时,需要处理一些额外的配置,请看下方章节。

处理外部依赖

为了对编译过程做一些定制,我们需要在项目根目录下创建一个Cross.toml文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
# 此处格式为 `target.<target-triple>`,可以存在多个不同目标的配置
[target.aarch64-unknown-linux-gnu]
# pre-build 可以在编译前执行一些命令,常用于安装依赖
pre-build = [
"apt-get update",
"apt-get install -y libssl-dev",
]
# 也可以用 image 来指定一个Docker镜像
image = "test-image"
# 指定一些环境变量
env.aaa = "bbb"

基本上配置方法就是如上,如果需要进一步定制,请参考官方文档

编译到MacOS

由于许可原因,cross-rs团队无法为我们提供预编译好的镜像,也不被允许分发Apple的SDK,所以我们需要自己以合法方式获取到SDK,进而编译一个镜像。下方我以aarch64-apple-darwin为例,其他平台类似。

编译镜像

我们需要克隆cross-rs的代码,并更新子模块,完成配置后构建镜像:

1
2
3
4
5
6
7
8
9
10
11
# Setup cross toolchains
git clone https://github.com/cross-rs/cross
cd cross
git submodule update --init --remote
cargo xtask configure-crosstool

# Build docker images
cargo build-docker-image aarch64-apple-darwin-cross --build-arg 'MACOS_SDK_URL=https://github.com/joseluisq/macosx-sdks/releases/download/12.3/MacOSX12.3.sdk.tar.xz' --tag local

# Compile our application
cross build --target aarch64-apple-darwin --release

构建对SDK版本要求如下:

  • i686-apple-darwin: SDK <= 10.13
  • x86_64-apple-darwin: SDK <= 13.0 or SDK <= 12.4
  • aarch64-apple-darwin: SDK >= 10.16 and (SDK <= 13.0 or SDK <= 12.4)

配置 Cross.tomml

构建完毕后,就很简单了,我们只需要在项目根目录下创建一个Cross.toml文件,指定对应目标使用我们的本地镜像,内容如下:

1
2
3
# Reference: https://github.com/cross-rs/cross-toolchains
[target.aarch64-apple-darwin]
image = "ghcr.io/cross-rs/aarch64-apple-darwin-cross:local"

后续的编译就和Linux一样了,不再赘述。

编译到Windows

MSVC或许是Windows下最优的目标,然而为了方便起见,我还是选择了GNU工具链。我们可以使用x86_64-pc-windows-gnui686-pc-windows-gnu作为目标,编译方法和Linux类似:

1
cross build --target x86_64-pc-windows-gnu --release

这样我们就能够在Linux或MacOS上编译出Windows下的可执行文件了,不需要配置MSVC。如果你非常想要MSVC的编译结果,请自行阅读官方仓库的README和Issue吧。

解决Tauri2无法拖拽

近期将我的游戏存档管理器迁移到Tauri2出现点问题,想起来以前V1的时候也遇到过这个问题,故记录下来。

具体问题就是在前端一个可拖拽组件没有办法被拖动,处理方式很简单,直接修改src-tauri/tauri.conf.json文件,将dragDropEnabled设为false,作为参考,请看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "game-save-manager",
"identifier": "com.game-save-manager",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:3000",
"beforeDevCommand": "pnpm web:dev",
"beforeBuildCommand": "pnpm web:generate"
},
"app": {
"windows": [
{
"title": "Game Save Manager",
"width": 1280,
"height": 720,
"resizable": true,
"fullscreen": false,
"dragDropEnabled": false
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/[email protected]",
"icons/icon.icns",
"icons/icon.ico"
]
}
}

Avalonia采用AOT编译后无法运行解决办法

近日在制作Avalonia程序过程中,发现调试时很正常,发布后却无法运行,感觉很奇怪,我的发布配置有以下两条:

1
2
<PublishTrimmed>True</PublishTrimmed>
<PublishAot Condition="'$(Configuration)' != 'Debug'">true</PublishAot>

第一条启用了裁剪,第二条启用了AOT编译(但是在开发时没开启,因为预览窗口不支持AOT)

经过一番排查,发现这两个都有问题:

  1. 反射对AOT不友好,我的ViewLocator.cs使用了反射来创建对象
  2. 有的库对剪裁支持不好,因此要对其关闭剪裁

解决方案

问题1: 消除反射

原代码使用反射创建对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
public Control? Build(object? param)
{
if (param != null && param is ViewModelBase)
{
var viewModelType = param.GetType();
if (_viewModelViewMappings.TryGetValue(viewModelType, out var viewType))
{
return (Control)Activator.CreateInstance(viewType)!; // 这里使用了反射
}
return new TextBlock { Text = "Not Found: " + viewModelType.FullName };
}
return null;
}

修改后使用工厂方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Control? Build(object? param)
{
if (param != null && param is ViewModelBase)
{
var viewModelType = param.GetType();
if (_viewModelViewMappings.TryGetValue(viewModelType, out var viewFactory))
{
return viewFactory(); // 使用了工厂方法
}
return new TextBlock { Text = "Not Found: " + viewModelType.FullName };
}
return null;
}

这样就可以消除反射了

问题2: 关闭特定库的剪裁

发布时我注意到很多这样的剪裁警告

1
2
3
4
5
2>Assembly 'Serilog' produced trim warnings. For more information see https://aka.ms/dotnet-illink/libraries
2>Assembly 'ReactiveUI' produced trim warnings. For more information see https://aka.ms/dotnet-illink/libraries
2>Assembly 'SukiUI' produced trim warnings. For more information see https://aka.ms/dotnet-illink/libraries
2>Assembly 'Avalonia.Controls.DataGrid' produced trim warnings. For more information see https://aka.ms/dotnet-illink/libraries
2>Assembly 'Avalonia.Controls.DataGrid' produced AOT analysis warnings.

因此我修改项目的sln文件,增加以下片段,通过外部的xml文件来限制剪裁

1
2
3
<ItemGroup>
<TrimmerRootDescriptor Include="TrimmerRoots.xml" />
</ItemGroup>

下面是TrimmerRoots.xml的内容

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8" ?>
<linker>
<assembly fullname="Serilog" />
<assembly fullname="Serilog.Sinks.Console" />
<assembly fullname="Serilog.Sinks.File" />
<assembly fullname="ReactiveUI" />
<assembly fullname="SukiUI" />
<assembly fullname="Serilog.Sinks.File" />
</linker>

我将所有报警告的项目都加入了进去,虽然后面的编译依旧有新的警告,但是程序可以正常运行了。至此,所有问题解决。

Avalonia Unable to resolve property or method of name 'xxx' on type 'XamlX.TypeSystem.XamlPseudoType'解决办法

近期我在使用Avalonia编写桌面程序时,用到了ItemRepeater组件,写出来大概是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:vm="clr-namespace:CooingOwl.ViewModels"
xmlns:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
x:DataType="vm:ExploreViewModel"
x:Class="CooingOwl.Views.ExploreView">

<ScrollViewer HorizontalScrollBarVisibility="Auto">
<ItemsRepeater ItemsSource="{Binding B}" Margin="16">
<ItemsRepeater.Layout>
<StackLayout Spacing="20"
Orientation="Vertical" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<suki:GlassCard>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}"/>
<TextBlock Margin="4 0" FontWeight="Bold"
Text="{Binding Id}"/>
</StackPanel>
</suki:GlassCard>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>

</UserControl>

其中<TextBlock Text="{Binding Name}"/>Text="{Binding Id}"/>产生了报错AVLN2000 Unable to resolve property or method of name 'Id' on type 'XamlX.TypeSystem.XamlPseudoType'.,查看类型ExploreViewModel,定义大概如下

1
2
3
4
5
6
7
8
9
public class ExploreViewModel : ViewModelBase
{
public A[] B { get; set; } = new A[] { ... };
}

public class A{
int Id,
string Name
}

造成该问题的原因是我需要使用的属性B是一个原始的数组,这里应该使用ObservableCollection因此解决方案是做出如下修改

1
2
3
4
public class ExploreViewModel : ViewModelBase
{
public ObservableCollection<Assistant> Assistants { get; set; } = new (new List<Assistant>{...});
}

至此能够成功编译运行

Scoop意外卸载的处理办法

这几天发现电脑上的Scoop莫名其妙消失了,我觉得可能是UniGetUI的问题,总之我的Scoop被卸载了,使用Scoop安装的软件的环境变量也失效了,这导致我写代码的相关环境无法使用。
在重新安装Scoop后,环境变量没有恢复,而我发现路径C:\Users\用户名\scoop没有丢失,那么说明软件文件和持久化数据应该没有丢失,寻找一些资料后,我在一篇Github Issue中找到了解决办法,如果你遇到了相同的问题,可以尝试按照以下步骤解决:

1
2
cd C:\Users\用户名\scoop\apps
scoop reset *

我在重新安装Scoop并且进行了该操作后,成功恢复了大多数软件对应的环境,除了一些我已经打开的一些软件没有恢复,不过后面也可以手动scoop reset 软件名来单独进行恢复

Nuxt.js content使用项目组件报错

问题描述

最近使用 Nuxt.js 的 Content 模块时,发现其支持项目中写好的 Vue 组件,相关文档可以参考 这里
我在页面中尝试添加如下组件,该组件位于~/components/XXX/VideoArea.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<script setup lang="ts">
import ArtPlayer from "artplayer"

const art_ref = ref<HTMLInputElement | null>(null)
const art_instance = ref<Artplayer | null>(null);

const props = defineProps<{ url: string }>()

onMounted(() => {
if (!art_ref.value) return
// 文档参考: https://artplayer.org/document/start/option.html
art_instance.value = new ArtPlayer({
// 指定播放器的容器元素
container: art_ref.value,
// 设置视频的 URL
url: props.url,
// 启用自动迷你模式
autoMini: true,
// 启用自动调整大小
autoSize: true,
// 启用全屏模式
fullscreen: true,
// 使用服务器端渲染
// useSSR: true,
// 启用快进功能
fastForward: true,
// 锁定播放器界面
lock: true
})
})
</script>

<template>
<div ref="art_ref" class="h-96 mr-auto">
<slot></slot>
</div>
</template>

对应 Content

1
2
3
4
5
6
7
---
title: '标题'
description: '简述'
---
::xxx-video-area{url="视频链接"}
视频描述
::

发现启动后报错 If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.,后来发现可能是没有设定全局组件,文档中有提到:

Components that are used in Markdown has to be marked as global in your Nuxt app if you don’t use the components/content/ directory, visit Nuxt 3 docs to learn more about it.
如果在Nuxt应用中使用的组件未放置在components/content/目录下,则需将其标记为全局组件。

对比一下,我的组件在~/components/XXX/下,而不是在~/components/content/下,因此无法访问到该组件。

解决方案

方案一:将组件移动到~/components/content/目录下

假设你的组件仅用于内容页面,可以将其移动到~/components/content/目录下,这样 Nuxt Content 模块会自动识别并加载这些组件。

方案二:将组件名称改为VideoArea.global.vue

参考官方文档,我们修改文件后缀名也可以达到全局化的效果

方案三:将所有组件默认设为全局

nuxt.config.ts中,我们需要增加组件选项,设为全局即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
devtools: { enabled: true },
css: ['~/assets/css/main.css'],

postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},

modules: ['@nuxt/content'],

// 为了适配 Nuxt content 必须全局注册
// 注意下面这块,上面的如果你和我不同不需要改动
components: {
global: true,
dirs: ['~/components']
},
})

在Win下编译适用于Linux的Rust程序

最近在尝试在 Windows11 下编译 Linux 可用的程序时遇到了不少问题,我这里总结一个简单的编译方法供大家参考(不过现在都有WSL了,是不是直接在上面编译更好?)

具体方法

安装工具链

rustup target add x86_64-unknown-linux-musl 使用 musl 进行静态连接工具链
你可以使用rustup target list指令来检查自己是否装好了工具链
检查工具链

修改 Cargo 配置

在 ~/.cargo/config.toml (如果没有请自己创建) 内加上

1
2
[target.x86_64-unknown-linux-musl]
linker = "rust-lld"

Cargo配置

进行编译和测试

使用cargo build --target=x86_64-unknown-linux-musl就可以进行编译你的项目了,或者也可以用cargo build --target x86_64-unknown-linux-musl --release来构建 release 模式的
进行编译
可见,很快就编译好了,下面我使用 WSL2 运行该程序测试正确性,代码如下(虽然这个不重要)
程序代码
运行结果如下
运行结果
可见,一切顺利,结束!

Rust萌新之路-变量、函数和判断

我推荐的布局介绍

我推荐使用 Vscode 再安装 Rust-Analyzer 插件进行刷题,记得在开刷之前先执行 rustlings lsp 来启用语法提示,下面给大家看看各个布局窗口的作用
在命令行运行rustlings指令
1号区域是终端,我在里面启动了 rustlings watch 来检查进度,2号区域是各个题目(所有的题目都在 exercises 文件夹内),3号区域就是代码编辑框了,这样简简单单就可以开始了

Intro 和 Variables

Intro 部分其实没什么好看的,主要是教你如何使用 Rustlings ,在终端中提示通过编译后,你把注释中的 // I AM NOT DONE 删掉就可以了

Variables 里面的东西也比较简单,主要就是考变量的初始化语法、未初始化不能使用、如何创建可变变量、重新申明变量、常量必须指明类型这些,基本上没有什么问题,就不赘述了。

Functions 和 If

Functions 内的内容也比较基础,主要考察了函数的创建、参数的定义和传入、返回值的定义方式、通过去掉分号来返回一个值
很轻松就能解决这一块,我也认为这一块不会出现什么问题,也不多讲了。

If 的第一题体现了我很喜欢的一点,就是 Rust 中的 if 语句是有返回值的,同样可以用去掉分号来表示返回,整体也可以作为函数的返回值,因此这题我的答案是这样的

1
2
3
4
5
6
7
pub fn bigger(a: i32, b: i32) -> i32 {
if a>b {
a
}else {
b
}
}

第二题也类似,答案如下

1
2
3
4
5
6
7
8
9
pub fn foo_if_fizz(fizzish: &str) -> &str {
if fizzish == "fizz" {
"foo"
} else if fizzish == "fuzz" {
"bar"
}else {
"baz"
}
}

不过这里其实有一个比较有意思的地方,返回值是一个&str,新手可能会忘记为什么这里不需要标注生命周期,请让我来解释一下,这是因为这个函数满足了生命周期省略规则,我认为这里满足的是“如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数”,所以我觉得函数签名应该等价于 pub fn foo_if_fizz<'a>(fizzish: &'a str) -> &'a str ,不过考虑到字符串字面量的生命周期是 'static ,我认为这里也可以是 pub fn foo_if_fizz(fizzish: &str) -> &'static str,具体是什么样希望能有大神指出。

Quiz1

终于到了第一个小测验了,这也会是第一篇的最后一题,这是关于上面的 Variables、Functions 和 If 的测验。
对于写完前面的你应该算是简简单单

1
2
3
4
5
6
7
fn calculate_price_of_apples(num:i32)->i32{
if num>40{
num
}else{
num*2
}
}

本章到此就结束了

有用的其他参考

Rust萌新之路-Rustlings的安装

专栏简介

我学习 Rust 已经有一小段时间了,Rustling 这个项目我认为是新手学完Rust基本语法,或者在看完 The Book (中文版) 后,一个很适合的练习题组,这个项目提供了75道 Rust 语言的小题目,提供了方方面面的考验,对于绝大多数题目也都有足够的提示,但是鉴于我没有找到使用中文介绍这些题目的文章,便打算自己开一篇介绍和讲解,本人有的实现可能不够好,或者讲解有谬误,希望各位斧正。

安装 Rustlings

既然你已经开始想要写 Rust 练习题,那么你应该已经安装过Rust了,就不做多的介绍了,如果你还没有安装,请参考官方的这个页面,可以先安装Visual Studio最新版后,勾选适用于桌面的C++开发,然后安装 Rustup 等工具。

接下来,打开 Rustlings 官方仓库 ,里面介绍了安装的方式,在 Mac OS 或者 Linux 操作系统上,安装 Rustlings 是很简单的

1
curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh | bash

使用这个命令就可以把它安装到你的电脑上,之后就可以使用 Rustlings 指令了。至于 Windows 下稍微复杂一点,你需要先激活 Powershell 的脚本运行权限,然后就可以运行安装指令了

1
2
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Start-BitsTransfer -Source https://raw.githubusercontent.com/rust-lang/rustlings/main/install.ps1 -Destination $env:TMP/install_rustlings.ps1; Unblock-File $env:TMP/install_rustlings.ps1; Invoke-Expression $env:TMP/install_rustlings.ps1

如果你不喜欢上面的安装方式,也可以手动安装,也没多麻烦,先下载这个 git 仓库,然后在里面运行 cargo install –path . 就行了,我使用 git clone 进行下载,指令可以参考如下(可以把5.2.1替换成最新版本)

1
2
3
git clone -b 5.2.1 --depth 1 https://github.com/rust-lang/rustlings
cd rustlings
cargo install --force --path .

这样,你就完成了 rustlings 的安装,尝试在命令行运行 rustlings ,它会给你反应

在命令行运行 rustlings 指令

基础使用

其实需要做的操作很少,你如果只是做题的话在 rustlings 目录下使用 rustlings watch 就够了,以下内容主要是我对官方操作解释的翻译(注意,你的指令一定要在 rustlings 目录下运行,不要到上级或者子目录)

你要做的所有题目都在 rustlings/exercises/ 文件夹里,当你想以官方推荐的顺序来做的话,运行

1
rustlings watch

它会持续按顺序检查题目,并且给出错误(每当你编辑过之后),你需要修好错误或者使代码通过编译,它就会进入下一个题目,直到所有题目完成,或者你关闭了它,如果你只想运行一次而非持续运行,你可以允许下一条指令

1
rustlings verify

如果你想手动指定它检查的题目,你可以用 run 指令

1
rustlings run 测试名

或者使用 run next 来检查下一个的

1
rustlings run next

当你遇到了不会的题目,需要一定帮助时,可以使用 hint 指令,它的用法和 run 差不多,可以指定名字或者使用 next 来查找下一个

1
2
rustlings hint myExercise1
rustlings hint next

list 指令可以用于检查你的进度

1
rustlings list

例如我的输出就是都做完了

我运行 rustlings list 的结果

另外有一个非常重要的,就是你会发现你在编辑时无法使用 Rust-Analyzer ,这怎么行?启用的方法官方仓库也有给出,就是运行

1
rustlings lsp

另外,卸载的方法如下:

  1. 先删除 rustlings 所在的文件夹
  2. 运行 cargo uninstall rustlings

有用的其他参考