Files
DefectingCat.github.io/content/posts/rust-intractable-diseases.mdx
xfy 256212423a fix(url): handle error on url preview
add new post Rust 疑难杂症
2024-03-23 12:29:59 +08:00

450 lines
12 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 疑难杂症
date: '2024-03-23'
tags: [Rust]
---
## 在 tauri 中为 command 使用自定义错误
在 tauri 的 command 中返回的任何值都必须要是可以 `Serialize` 的包括错误Everything you return from commands must implement `Serialize`
```rust
use base64::DecodeError;
use serde::{Serialize, Serializer};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum VError {
#[error("Request error: {0}")]
RequestFaild(#[from] reqwest::Error),
#[error("Decode error: {0}")]
DecodeError(#[from] DecodeError),
}
// https://github.com/tauri-apps/tauri/discussions/3913
impl Serialize for VError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
pub type VResult<T, E = VError> = anyhow::Result<T, E>;
```
## async 语句中使用 `?`
async 语句块与 async fn 最大的不同就是前者无法显式的声明返回值,当配合 `?` 使用的时候编译器就无法得知 `Result<T, E>` 中 E 的类型。
```rust
async fn foo() -> Result<u8, String> {
Ok(1)
}
async fn bar() -> Result<u8, String> {
Ok(1)
}
pub fn main() {
let fut = async {
foo().await?;
bar().await?;
Ok(())
};
}
```
这段代码会得到编译器 `cannot infer type for type parameter E declared on the enum Result` 的提示。原因在于编译器无法推断出 `Result<T, E>` 中的 `E` 的类型。
目前的解决办法就是使用 `::<...>` 显式的添加类型注解,给予类型注释后此时编译器就知道 `Result<T, E>` 中的 `E` 的类型是 `String`,进而成功通过编译。
```rust
let fut = async {
foo().await?;
bar().await?;
Ok::<(), String>(()) // 在这一行进行显式的类型注释
};
```
## 数组元素非基础类型
Rust 的数组类型是存储在栈内存中的,但是它也是可以存储非基础类型的值。不过它不可以使用这样的数组初始化语法:
```rust
let a = [3; 5];
```
`let a = [3; 5];` 的本质是 Rust 不断的 Copy 出来的,而非基本类型是无法深拷贝的。
正确的做法应该是使用 `std::array::from_fn` 。
```rust
fn main() {
let arr: [String; 6] = core::array::from_fn(|i| format!("Arr {}", i));
println!("{:?}", arr);
}
```
## 在 map 方法中使用 `?`
在 map 方法中使用 Question mark 需要在 `collect::<>()` 方法中显示注解 `Result<T, E>`
```rust
let subscripition = subscripition
.split('\n')
.filter(|line| !line.is_empty())
.map(|line| {
let line = line.replace("vmess://", "");
let line = general_purpose::STANDARD.decode(line)?;
let line = String::from_utf8_lossy(&line).to_string();
Ok(serde_json::from_str::<Node>(&line)?)
})
.collect::<VResult<Vec<_>>>()?;
```
## 优雅的修改 Option 的内容
## 为 `enum` 实现静态字符串
```rust
#[derive(Debug, Serialize, Deserialize)]
pub enum CoreStatus {
Started,
Restarting,
Stopped,
}
impl CoreStatus {
fn as_str(&self) -> &'static str {
match self {
CoreStatus::Started => "Started",
CoreStatus::Restarting => "Restarting",
CoreStatus::Stopped => "Stopped",
}
}
}
```
## serde-rs deserialize string in json to number
```rust
use std::fmt::Display;
use std::str::FromStr;
use serde::de::{self, Deserialize, Deserializer};
#[derive(Deserialize, Eq, PartialEq, Debug)]
struct Test {
#[serde(deserialize_with = "from_str")]
test: u16
}
fn from_str<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where T: FromStr,
T::Err: Display,
D: Deserializer<'de>
{
let s = String::deserialize(deserializer)?;
T::from_str(&s).map_err(de::Error::custom)
}
```
## serde deserialize multiple type
https://www.reddit.com/r/rust/comments/fcz4yb/how_do_you_deserialize_strings_integers_to_float/
```rust
fn de_str<'de, D>(deserializer: D) -> Result<i64, D::Error>
where
D: serde::de::Deserializer<'de>,
{
Ok(match Value::deserialize(deserializer)? {
Value::String(s) => s.parse().map_err(de::Error::custom)?,
Value::Number(num) => num.as_i64().ok_or(de::Error::custom("Invalide number"))?,
_ => return Err(de::Error::custom("wrong type")),
})
}
fn de_str_option<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
Ok(match Value::deserialize(deserializer)? {
Value::String(s) => s.parse().map(Some).map_err(de::Error::custom)?,
Value::Number(num) => num.as_i64(),
_ => return Err(de::Error::custom("wrong type")),
})
}
```
## DerefMut 获取结构体多个字段 mut
在使用诸如 `Arc<Mutex<T>>` 的数据时,当获取到了锁的引用后,实际修改数据时,修改的是通过锁的 `DerefMut` 来修改其数据的。
```rust
let mut config = config.lock().await;
config.rua = "test";
```
而当数据中有多个字段需要分别获取为 `mut` 时,则编译器可能无法正确的认识到实际获取的是不同的字段,而非将目标数据获取了多次 `mut`。
```rust
let mut config = config.lock().await;
let mut rua = &mut config.rua;
let mut core = config.core.as_mut().unwrap();
// cannot borrow `config` as mutable more than once at a time
```
这是因为 `DerefMut` 让编译器认为我们获取了两次 `config` 结构体为 `mut`,而非它的两个不同的字段。最佳解决办法就是通过手动解引用来获取到实际的结构体,从而使得编译器能够正确的认识到引用到字段。
```rust
let mut config = config.lock().await;
let config = &mut *config;
let mut rua = &mut config.rua;
let mut core = config.core.as_mut().unwrap();
```
虽然自动解引用看上去获取的字段都是正确解引用的,但是编译器可能无法正确的识别到这两个字段是不同的。
![[Pasted image 20230730004909.png]]
这可能是因为 `Mutex` 使用的是 `UnsafeCell<T>` 来获取实际值的可修改引用。
```rust
pub struct Mutex<T: ?Sized> {
#[cfg(all(tokio_unstable, feature = "tracing"))]
resource_span: tracing::Span,
s: semaphore::Semaphore,
c: UnsafeCell<T>,
}
impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.lock.c.get() }
}
}
```
## 异步递归
在内部,`async fn` 创建了一个包含了要 `.await` 的子 `Future` 的状态机。这样递归的 `async fn` 有点诡异,因为结果的状态机必须包含它自身:
```rust
#![allow(unused)]
fn main() {
async fn step_one() { /* ... */ }
async fn step_two() { /* ... */ }
struct StepOne;
struct StepTwo;
// This function:
async fn foo() {
step_one().await;
step_two().await;
}
// Generates a type like this:
enum Foo {
First(StepOne),
Second(StepTwo),
}
// So this function:
async fn recursive() {
recursive().await;
recursive().await;
}
// Generates a type like this:
enum Recursive {
First(Recursive),
Second(Recursive),
}
}
```
但是这将无法工作,因为我们创建了无限大小的类型
```
error[E0733]: recursion in an `async fn` requires boxing
--> src/lib.rs:1:22
|
1 | async fn recursive() {
| ^ an `async fn` cannot invoke itself directly
|
= note: a recursive `async fn` must be rewritten to return a boxed future.
```
为了允许这种做法,我们需要用 `Box` 来间接调用。而不幸的是,编译器限制意味着把 `recursive()` 的调用包裹在 `Box::pin` 并不够。为了让递归调用工作,我们必须把 `recursive` 转换成非 `async` 函数,然后返回一个 `.boxed()` 的异步块
```rust
use futures::future::{BoxFuture, FutureExt};
fn recursive() -> BoxFuture<'static, ()> {
async move {
recursive().await;
recursive().await;
}.boxed()
}
```
## 消耗一个异步 map
当一个迭代器中产出异步值的时候,除了使用 for 循环来在外部函数中 await 它,还可以使用 `futures` crate 中的 `try_join_all`。
```rust
use futures::future::try_join_all;
#[tokio::main]
async fn main() {
let urls = ["https://www.google.com", "https://rua.plus"];
let sites =
try_join_all(urls.map(|url| async move { reqwest::get(url).await.unwrap().text().await }))
.await
.unwrap();
sites.iter().for_each(|site| {
println!("{}", site.len());
});
}
```
## 在 `fold` 方法中使用 `?`
`fold` 方法本身无法使用 `?`,但是 `fold` 还有另一个变体,可以允许直接使用 `?``try_fold`。
https://doc.rust-lang.org/std/ops/trait.Try.html
The `?` operator and `try {}` blocks.
`try_*` methods typically involve a type implementing this trait. For example, the closures passed to [`Iterator::try_fold`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.try_fold 'method std::iter::Iterator::try_fold') and [`Iterator::try_for_each`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.try_for_each 'method std::iter::Iterator::try_for_each') must return such a type.
`Try` types are typically those containing two or more categories of values, some subset of which are so commonly handled via early returns that its worth providing a terse (but still visible) syntax to make that easy.
This is most often seen for error handling with [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html 'enum std::result::Result') and [`Option`](https://doc.rust-lang.org/std/option/enum.Option.html 'enum std::option::Option'). The quintessential implementation of this trait is on [`ControlFlow`](https://doc.rust-lang.org/std/ops/enum.ControlFlow.html 'enum std::ops::ControlFlow').
```rust
pub async fn regular_post() -> Result<BaiduZZ> {
let site = env::var("SPIO_SITE")?;
let token = env::var("SPIO_TOKEN")?;
let url = format!("{}?site={}&token={}", BAIDU_URL, site, token);
let now = Instant::now();
info!("starting get booths, request /admin/Apiview/sample_view");
let params = ExampleParam::default();
let examples = get_examples(&params).await?;
let urls = examples
.data
.list
.data
.iter()
.try_fold(vec![], build_booth_url)?
.join("\n");
info!("parse booth's url done {}ms", now.elapsed().as_millis());
let res: BaiduZZ = reqwest::Client::new()
.post(url)
.header(reqwest::header::CONTENT_TYPE, "text/plain")
.body(urls)
.send()
.await?
.json()
.await?;
Ok(res)
}
fn build_booth_url(prev: Vec<String>, cur: &BoothExamplesDatum) -> Result<Vec<String>> {
let url = Url::parse(cur.link.as_ref().unwrap_or(&PRODUCTION_URL.to_owned()));
let host = url?;
let host = host.host_str().ok_or(anyhow!("parse url failed"))?;
let booth_url = if host.contains("aiyunhuizhan") {
format!(
"https://{}/optimize/booth_id/{}",
host,
cur.booth_id
.as_ref()
.ok_or(anyhow!("cannot read booth id"))?
)
} else {
cur.link.to_owned().expect("")
};
anyhow::Ok([prev, vec![booth_url.to_string()]].concat())
}
```
同理,返回 `std::ops::Try` 的方法都能过使用 `?`。
```rust
use anyhow::anyhow;
fn main() -> anyhow::Result<()> {
let data = [Some(123), Some(444), None];
data.iter().try_for_each(|x| {
let real_x = x.ok_or(anyhow!(""))?;
dbg!(real_x);
anyhow::Ok(())
})?;
Ok(())
}
```
## 传递 async funcation as paramters
```rust
use futures::Future;
#[tokio::main]
async fn main() {
let num = test(calc).await;
println!("{num}");
}
async fn calc() -> i32 {
40 + 2
}
async fn test<F, Fut>(f: F) -> i32
where
F: FnOnce() -> Fut,
Fut: Future<Output = i32>,
{
f().await
}
```
## BoxedFuture
```rust
pub type Response = BoxFuture<'static, (Status, Bytes)>;
type Job = fn(Request) -> Response;
type Routes = Arc<RwLock<HashMap<&'static str, HashMap<&'static str, Job>>>>;
```
## Axum trailing slashes in route path
https://github.com/tokio-rs/axum/discussions/2377
```rust
use axum::{extract::Request, Router, ServiceExt};
use tower::Layer;
use tower_http::normalize_path::NormalizePathLayer;
#[tokio::main]
async fn main() {
let app = Router::new();
let app = NormalizePathLayer::trim_trailing_slash().layer(app);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, ServiceExt::<Request>::into_make_service(app)) // <-- this
.await
.unwrap();
}
```