Rust 总结:格式化

https://github.com/mattwhatsup/rust-practice-and-notes/tree/main/print-and-format
https://rustwiki.org/zh-CN/rust-by-example/hello/print.html

相关模块

std::fmt

  • format!:将格式化文本写到字符串。
  • print!:与 format! 类似,但将文本输出到控制台(io::stdout)。
  • println!: 与 print! 类似,但输出结果追加一个换行符。
  • eprint!:与 print! 类似,但将文本输出到标准错误(io::stderr)。
  • eprintln!:与 eprint! 类似,但输出结果追加一个换行符。
  • fmt::Debug:使用 {:?} 标记。格式化文本以供调试使用。
  • fmt::Display:使用 {} 标记。以更优雅和友好的风格来格式化文本。

一些常见格式的罗列

fn main() {
    println!("{}", 1); // 默认用法,打印Display
    println!("{:o}", 9); // 八进制
    println!("{:x}", 255); // 十六进制 小写
    println!("{:X}", 255); // 十六进制 大写
    println!("{:p}", &0); // 指针
    println!("{:b}", 15); // 二进制
    println!("{:e}", 10000f32); // 科学计数(小写)
    println!("{:E}", 10000f32); // 科学计数(大写)
    println!("{:?}", "test"); // 打印Debug
    println!("{:#?}", ("test1", "test2")); // 带换行和缩进的Debug打印
    println!("{a} {b} {b}", a = "x", b = "y"); // 命名参数


    assert_eq!(format!("Hello {:<5}!", "x"),  "Hello x    !"); // <右边界宽度
    assert_eq!(format!("Hello {:-<5}!", "x"), "Hello x----!"); // <右边界宽度+填充
    assert_eq!(format!("Hello {:^5}!", "x"),  "Hello   x  !"); // ^居中
    assert_eq!(format!("Hello {:>5}!", "x"),  "Hello     x!"); // >右边界宽度

    println!("Hello {:+}", 5); // +显示正号
    println!("{:#x}!", 27); // #显示十六进制
    assert_eq!(format!("Hello {:05}!", 5),  "Hello 00005!"); // 宽度
    println!("{:#09x}!", 27); // 十六进制数字宽度
    println!("Hello {0} is {1:.5}", "x", 0.01); // 小数位

    // $代入符号
    println!("Hello {0} is {1:.5}", "x", 0.01);
    // 0的位置是5,1的位置是x,2的位置是0.01
    println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
    // 0的位置是x,1的位置是5,2的位置是0.01
    println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
    // *统配代入符号
    // 0的位置是x,1的位置是5,2的位置是0.01
    println!("Hello {} is {:.*}",    "x", 5, 0.01);
    // 0的位置是5,1的位置是x,2的位置是0.01
    println!("Hello {1} is {2:.*}",  5, "x", 0.01);

    // 转义{}
    assert_eq!(format!("Hello {{}}"), "Hello {}");
    assert_eq!(format!("{{ Hello"), "{ Hello");
}

Debug trait

#[derive(Debug)] 所有类型都能推导fmt::Debug 的实现

// `derive` 属性会自动创建所需的实现,使这个 `struct` 能使用 `fmt::Debug` 打印。
#[derive(Debug)]
struct DebugPrintable(i32);

使用 {:#?} 美化打印

Display trait

fmt::Display 需要自己实现

// (使用 `use`)导入 `fmt` 模块使 `fmt::Display` 可用
use std::fmt;

// 定义一个结构体,咱们会为它实现 `fmt::Display`。以下是个简单的元组结构体
// `Structure`,包含一个 `i32` 元素。
struct Structure(i32);

// 为了使用 `{}` 标记,必须手动为类型实现 `fmt::Display` trait。
impl fmt::Display for Structure {
    // 这个 trait 要求 `fmt` 使用与下面的函数完全一致的函数签名
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 仅将 self 的第一个元素写入到给定的输出流 `f`。返回 `fmt:Result`,此
        // 结果表明操作成功或失败。注意 `write!` 的用法和 `println!` 很相似。
        write!(f, "{}", self.0)
    }
}

如果想实现 {:b} 打印二进制则需要自己实现 std::fmt::Binary

Rust学习的关键资源

⭐️ Rust-Learning repo https://github.com/ctjhoa/rust-learning

官方教程(号称The Book)https://doc.rust-lang.org/stable/book/
中文版 https://kaisery.github.io/trpl-zh-cn/ https://rustwiki.org/zh-CN/book/

Easy Rust 用比较简单的英语写的教程,内容也较简单一些(入门)
https://dhghomon.github.io/easy_rust/Chapter_0.html

菜鸟Rust教程 https://www.runoob.com/rust/rust-tutorial.html

Rust By Example https://doc.rust-lang.org/rust-by-example/hello.html
⭐️ 中文版 https://rustwiki.org/zh-CN/rust-by-example/index.html

⭐️ Rust Cookbook https://rust-lang-nursery.github.io/rust-cookbook/

中文版:https://rustwiki.org/zh-CN/rust-cookbook

⭐️ Rust By Practice( Rust 练习实践 ) https://zh.practice.rs/why-exercise.html

⭐️ Rust圣经 https://course.rs/about-book.html

Rustc Book https://doc.rust-lang.org/rustc/index.html

Rust 🦀 and WebAssembly https://rustwasm.github.io/book/

The wasm-bindgen Guide https://rustwasm.github.io/wasm-bindgen/introduction.html

Rust参考手册:

英文 https://doc.rust-lang.org/reference/introduction.html

中文 https://rustwiki.org/zh-CN/reference/introduction.html

Rust 宏小册 https://zjp-cn.github.io/tlborm/

Rust 中的异步编程 https://huangjj27.github.io/async-book/index.html

Rust 秘典(死灵书) https://nomicon.purewhite.io/intro.html

官方API文档 https://doc.rust-lang.org/std/index.html

标准库中文版 https://rustwiki.org/zh-CN/std/

优质Rust项目 https://github.com/rust-unofficial/awesome-rust
中文版 https://github.com/PuzzledAlien/awesome-rust-cn

Rust 中文资源 https://github.com/rust-lang-cn

Effective Rust https://www.lurklurk.org/effective-rust/cover.html

⭐️ RustGYM https://rustgym.com/ leetcode题目的rust解法

⭐️ Rustlings https://github.com/rust-lang/rustlings 官方出品的rust小练习,可以有针对的练习各个语法点

Rust Atomics and Locks:Low-Level Concurrency in Practice https://marabos.nl/atomics/

The Tauri Documentation Work-in-progress https://jonaskruckenberg.github.io/tauri-docs-wip/introduction.html

Comprehensive Rust https://google.github.io/comprehensive-rust/
安卓团队编写的为期四天的 Rust 课程,从基本语法到高级主题,最后一天还会讲到 Rust 如何用于安卓开发。

视频:Rust Linz https://www.youtube.com/watch?v=OhCjnyBc448&list=PL85XCvVPmGQgL3lqQD5ivLNLfdAdxbE_u

视频:The Rust Lang Book https://www.youtube.com/watch?v=OX9HJsJUDxA&list=PLai5B987bZ9CoVR-QEIN9foz4QCJ0H2Y8&index=1

EPUB格式使用自定义字体

首先准备字体文件,放入 OEBPS/Fonts 文件夹

提供CSS,并使用字体

@font-face {
	font-family: '冬青黑体';
	src: url(../Fonts/DongQingHeiTi-1.otf);
}

@font-face {
	font-family: '汉仪隶书';
	src: url(../Fonts/hanyilishu.ttf);
}

META-INF/com.apple.ibooks.display-options.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<display_options>
	<platform name="*">
		<option name="specified-fonts">true</option>
	</platform>
</display_options>

这样在苹果系统的图书App里就可以使用“原字体”的设置方式。

注意:一定是这样

<option name="specified-fonts">true</option>

我因为用vscode保存时自动格式化,ibook是不认这样的变量的!浪费我一个多小时😷

<option name="specified-fonts">
  true
</option>

被Google坑了

今天登录自己的cruelyouth.com域名的邮箱失败,是在gmail上用自己域名注册的邮箱,本来用了十几年都相安无事,今天一登录发现说我过了14天的新手使用期,要续费,我用了14年都不止,何来14天的试用期呢?看来是google把自己这点免费的东西撮成一堆儿打算赚点钱,可怜我这个邮箱很多的重要账号都是用它做的账号,现在收费说这个域名下我创建了3个用户头一年每个用户每月收$3,从明年则是每月$6,看来要自己找时间去架一个email server了,没什么是永远靠得住的,包括google。

当React 18出现,create-react-app 创建项目遇到的小问题

一不留神,发现react版本已经升级至18,create-react-app里的react版本已经更新为18,但有一些模版还没改过来。

第一,如果安装模块说依赖于react@17.0的话,可以–force强制安装

npm i package-name --force

第二,v18修改了react-dom的使用方法,但因为用ts的模版创建的项目,react和react-dom的types还是老的,报告没有react-dom/client模块,因此在package.json中,删掉dependencies里的@types/react和@types/react-dom,重新安装

npm i @types/react @types/react-dom

这样再使用react-dom/client就可以了

import { createRoot } from 'react-dom/client'

const root = createRoot(document.getElementById('root') as Element)
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
)
无聊的谐音梗,“码”被提升

奇怪的变量提升现象

学生提了一个问题

var a = 100
{
  a = 10
  function a() {}
  a = 20
  console.log(a) // 20 
}
console.log(a) // 10

最后打印20和10。这确实是一个奇怪的现象,因为按照以前对函数提升的理解,函数的声明部分会提升到生命周期的最前,赋值会提升到代码块的最前,这个例子显然不符合这个规则,那是怎么回事呢?

stackoverflow上有相同问题,看过答案后,现在我对这段代码的理解是,function的声明在代码块中的提升会产生两个提升,一个是到生命周期的最前端,一个是到代码块的最前端成为一个局部变量(生命周期是块,类似于let),所以形成内外两个生命周期的变量。而且function提升的位置永远比同var更靠前。

这样上面的代码就变成(a1表示外部生命周期的a,a2表示块作用域中的a)

var a1 <-- function 提升到外部的声明
a1 = 100
{
  function a2() {}
  a = 10 <-- 解释器解释到这时候a指向a1,所以a1=10
  a1 = a2 <-- function留在原地的部分要用a1赋值a2
  a = 20 <-- 解释器解释到这的时候a2已经提升了,所以它是a2=20
  console.log(a) // 20 <-- 这里生命周期较近的是a2,所以打印20
}
console.log(a) // 10 <-- 这里是a1,打印10

另外当function在块作用域中提升后,效果类似于let,所以还有这样的效果

{
  var x = 1
  function x() {} // 报错,重复声明
}

还有这样一个效果

{ //block 1
  function foo() { // declared in block 1
    return 1;
  }
  console.log("block 1: foo() === 1", foo() === 1);
  
  { // block 2
    function foo() { // declared in block 2
      return 2;
    }
    console.log("block 2: foo() === 2", foo() === 2);
  }
  
  console.log("block 1: foo() === 1", foo() === 1);
}
console.log("block 1: foo() === 1", foo() === 1);

/* 结果
block 1: foo() === 1 true
block 2: foo() === 2 true
block 1: foo() === 1 true
block 1: foo() === 1 true
*/

运行到block 2(7到12行位置的function foo),发现全局有一个变量已经叫foo,并且它处于block1处的局部变量foo1控制之下,所以会在此代码块里生成一个局部变量foo2(类似let声明的)而不覆盖全局的foo并且区分开和foo1的生命周期,所以造成这种现象。

如果注释掉2到5行,则全局变量foo也会被赋值为 block2处的foo。

总之,推荐用"use strict"可以减少这种奇怪的现象

<script>
"use strict"
var a = 100
{
  a = 10
  function a() {}
  a = 20
  console.log(a) // 20
}
console.log(a) // 100
</script>

useSelector优化

在react函数组件中如果要访问state上的值应该用useSelector,这个函数本身使用很简单,但要注意优化,避免产生性能问题。

基本使用方法

const selectTodos = state => state.todos

const todos = useSelector(selectTodos)

文档上给出的注意要点:

注意:useSelector的结果会用 === 对比,所以如果返回值不同就会导致组件重新渲染,如果不小心在不必要返回新对象的时候返回了新的对象,你的组件会在每次action被触发的时候都重新刷新。

错误做法

// 错误做法
const selectTodoDescriptions = state => {
  // 每次返回一个新的对象
  return state.todos.map(todo => todo.text)
}

因为array.map方法返回的是新的对象,因而虽然每次的内容相同,但是返回的结果用===比较时是不同的,因此每次都会重新渲染。

正确做法

// 正确做法
const todos = useSelector(state => state.todos)

仍然存在问题

这样虽然能解决一点问题,但是对于需要过滤的selector却是无效,因为不论使用什么方法,filter过后的值总是会返回一个新的对象

const filteredTodos = (state, completed) => 
  state.todos.filter(todo => todo.completed === completed)

并且这种情况下使用useMemo也是比较麻烦的。

所以我们要解决的问题是,当选择的条件不变,并且相关数据也没有变化的时候,返回相同的数据。

所幸已经有人解决了这个问题,就是使用reselect库,@reduxjs/toolkit 也已经集成了这个库。

使用createSelector

假设有这样结构的state

{
  users: {ids: ['id1', 'id2'], entities: {id1: user1, id2: user2}}
}

如果使用普通的selector取得用户列表,在render方法中使用useSelector:

const users = useSelector((state) => {
  // run every times when rerender
  console.log("users selector called");
  return state.users.ids.map((id) => state.users.entities[id]);
});

这样虽然能达到效果,但是如果父组件刷新,不但每次都会执行selector,并且还会每次返回一个新的数组,导致列表重新渲染(虽然子组件如果用memo处理仍然是同一个user对象,props不变的情况下不会重新渲染)。

如果用了createSelector,可以解决这个问题。createSelector的调用方式如下:

createSelector(
  depends1, 
  depends2,
  ...,
  selector(depends1result, depends2result, ...) {
    return 结果
  }
)

depends的结构为:

(state, ...args) => dependsResult

args 就是传给selector的额外参数

如果没有任何depends,则selector的形式就是上面的depends函数形式。

之所以需要上面的depends,是因为如果每个依赖项的返回值跟上一次都一样的话,selector就直接返回缓存后的结果,避免重新运算。所以用createSelector版本写出来就是这样:

import { createSelector } from "@reduxjs/toolkit";

export const usersSelector = createSelector(
  (state) => state.users.ids,
  (state) => state.users.entities,
  (_, filter) => filter,
  (ids, users, filter) => {
    // when any of ids, users, filter arguments changes, this selector will run
    console.log("users selector called");
    switch (filter) {
      case "male":
      case "female":
        return ids
          .map((id) => users[id])
          .filter((user) => user.gender === filter);
      default:
        return ids.map((id) => users[id]);
    }
  }
);

这里还加入了filter参数,只要ids/entities/filter参数都没变化,则selector返回的结果不会重新计算,达到了优化效果。

更复杂的完整例子见链接:
https://codesandbox.io/s/useselector-demo-1q18xg?file=/src/App.js

战争,是最无耻的罪行

俄罗斯入侵乌克兰,对网络上很多中国人的吃瓜群众态度感到失望,他们只看这次战争是一场谈资,一个热闹,还有不少人热切盼望台湾是下一个乌克兰,表现出来的兴奋,对此我深深感到羞愧,这些人妄自尊大,可怕,根本不知道自己在盼望什么,可耻!

黑暗的世代即将来到,但光明也不远了,提醒我们更要为自己的言行负责,凡事都有报应,那些为一己私利而使用暴力的人必然受到审判。