owtotwo 最近的时间轴更新
owtotwo

owtotwo

V2EX 第 175996 号会员,加入于 2016-06-04 20:21:01 +08:00
owtotwo 最近回复了
最简化问题

先达成共识 看 nuphy 官网的宣传页面 https://nuphy.com/collections/keyboards/products/gem80 里面的图有标注键盘特色功能(主要是支持 qmk/via 以及有物理切换 Mac/Win 的开关)
我用的是 nuphy air60 v2 同一家的产品 理论上 qmk 固件的内容跟 gem80 应该是差不多的
Win 模式的默认层是第 3 层(0~7 共 8 层) 此问题不需要更多层 所以只考虑 mac 模式的层(即 0 、1 、2 层)

如果我没理解错楼主题意 即 nuphy gem80 默认的 mac 模式的默认层(0 层)的 F1~F12 默认是 mac 上的特殊功能 比如 F1 是亮度减(对应 via 的"Screen-" 在 SPECIAL 里) F3 是 task(对应 via 的"Mac Task" 在 CUSTOM 里) 单按一下 F2 就能亮度加 而 Fn+F2 键才是真正输出"F2"
但楼主希望 F2 键按一下就是"F2" 而非"Screen+" 反而 Fn+F2 键才是亮度加
Mac 模式下键盘上的 Fn 其实就是 0 层里(临时)切换至 1 层的 via 键"MO(1)" 按住切换后再按 F2 就是 1 层的 F2 键上的内容

*解:所以最直接解法就是 在原本默认没有动过 via 配置的初始情况下 将 0 层和 1 层的 F1~F12 直接一一对调 就完成楼主的需求了

补充:nuphy 因为比较往 Mac 用户上靠 所以连默认模式都是 mac 键帽图标也是 mac 的快截效果键(如 Mac Search 这个 F4 上的放大镜图标) 所以 CUSTOM 里也提供了多个 Mac 专用快捷键
另外 nuphy 的 via 就是 qmk 那个 所以能实现的效果很完整 加上对 Mac 的高适配 因此改键的自由度很高 基本你想实现的都能改(非常复杂的需要改 qmk 固件 但是应该是用不到的)

b 站搜一下 via 多层改键的视频就好了(比如 BV1jv4y1o7pk 或 BV1RV4y197s4)
对比字体渲染效果 不是应该
相同字体(如小米字体 MiSans 俩系统都改字体)
相同网页(或能调排版的软件)调好 css 字体参数(大小间隔字重等等)
同一屏幕下(24 寸 4k 苹果 hidpi 微软 200%缩放)
用手机的微距镜头拍屏幕字体吗?(非截图)
把结果图放上来 一切不就清楚了

我对现今 macOS 了解不深 还停留在几年前的黑苹果印象 但当时就是同一屏幕下(24 寸 4k)双系统(虽然都是默认字体)
因为是普通 IPS 屏 没有 oled(紫边)或其他屏幕等像素排列之类的差异
感觉苹果 UI 很舒适很有设计感和整体性 但别人提到的字体渲染问题 我感觉两者都足够清晰锐利 没有字体边缘的各种问题(不过我更喜欢苹果默认字体)
(可能印象模糊了 但 Win10 的 1909 到现在 Win11 的 23H2 我感觉新软件的高分屏缩放导致的问题愈发减少了 至少现在很舒适)
所以我的个人浅显的主观结论是 Win 的 200%缩放下字体没问题 retina 级别屏下与 masOS 差异不至于有一眼的差距
250 天前
回复了 caobug 创建的主题 Rust RUST 所有权移动问题
@caobug #22
有讲清楚了就好 能对别人有帮助还是很开心的 : )

Rust 文章的话 不太好写 写起来时很难兼顾到不同熟练度的 Rust 小伙伴(主要还是我自己能力有限)

举个例子
就像一开始其实我想**直接**用 `while true {}`和`loop {}`为什么不一样 来解释这问题的(前者迭代 0+次 后者 1+次)
但是这样会引入题目中没提到的 while 语句 以及它们另外的差异( loop-break <value>能返回值而 while 恒为`()`之类的) 最后还得再迁移到 for 语句来解释

这样就有可能将问题复杂化了 而如果跳过中间例子直接说“const_expr 在 borrowck 阶段不求值” 不熟悉的小伙伴有可能一下子转不过来

且问题涉及 if 语句 所以最后决定用`if false {}`作例子来渐进地解释 比较好理解

Rust 文章同理 讲一个点 从多浅讲到多深 我就有点犯难了
有的知识点实在涉及太多 比如 Pin 感觉没十几页纸实在讲不清楚 一想就头都大了 0.o
250 天前
回复了 caobug 创建的主题 Rust RUST 所有权移动问题
@DianQK #21
嗯呐 这个确实就是关键所在

若我没记错的话 似乎 rustc 的编译流程会有两次的常量折叠/传播 一次是在前端的 MIR 中 另一次是在后端如 LLVM 中
(好像是前端优化一次能降低给后端的 IR 代码复杂度)

MIR 中支持常量传播应该是比较早前的事了(或许有相关公告) 似乎是支持控制流的(代码可能在 mir 部分的 const_prop.rs ?文件名应该长得差不多)
但并不知道是否支持“消掉 if const_expr”的行为
(我不知道这种分支优化的术语应该是什么 死代码消除 Dead Code Elim ?或者是叫 Sparse Cond Const Prop ?中文可能是 稀疏条件常量传播 之类的 或许也不准确)

但比较尴尬的是 常量传播是在 MIR 的优化阶段进行的 而 borrowck 是在 mir-opt 之前进行的(如果我没记错的话)

所以正如老哥你所说的 常量传播时应该已经有借用检查了
(以及我感觉理论上应该确实是能在借用检查前算 const 的 就是不知道最终会不会增加 MIR 部分编译的总耗时)

编译流程层面的改动影响对 rustc 而言还是挺大的(如 Polonius 也只是 borrowck 部分的平替) 所以短期内可能不会有相应优化了(个人感觉 不知道目前有没有人提对应的 RFC )


以上的话并不严谨 我也暂时没能去进行校验 或许会有些错漏或过时(记忆有点旧了)

有条件的朋友或许可以补充下相关链接~
251 天前
回复了 caobug 创建的主题 Rust RUST 所有权移动问题
@DianQK #17
或许有些区别
简单点概况,NLL(Non-Lexical Lifetimes)的迭代进化(即下一代的 Polonius)应该依然并不能解决此问题。

因为楼主这问题本质上是 rustc 编译流程的限制。如图: https://blog.rust-lang.org/images/2016-04-MIR/flow.svg
根据**目前**的编译流程,Borrow Checking 发生在 MIR 阶段,此刻的 CFG(Control-Flow Graph)仅将`if true {}`识别为`if some_cond {}`。
故而`if true { <code> }`无法等价于`loop { <code>; break }`或`{ <code> }`,因为它们的 CFG 是不一致的。(参考引用 数据流分析中的 CFG )
最后将类似`if false {}`这样的死代码消除的优化行为,是在 LLVM 的 codegen 优化阶段进行的,所以此前 borrowck 并不认识"true"或"false"。

在 Rust 2018(Rust 1.31)引入 NLL 后,生命周期的推断精度更高了,而下一代的 Polonius 会支持更复杂的控制流(Control Flow)。但是如上所述的原由,依然不能在此阶段进行条件求值,所以问题依在。

* NLL: https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html#non-lexical-lifetimes
* 编译流程中的 MIR: https://blog.rust-lang.org/2016/04/19/MIR.html
* 数据流分析中的 CFG: https://github.com/rust-lang/rustc-dev-guide/blob/master/src/appendix/background.md#what-is-a-dataflow-analysis
* rustc 概览(包含各编译阶段): https://rustc-dev-guide.rust-lang.org/overview.html


我希望能由浅入深解释问题,但无法太深。前面的回答并无涉及到更多的编译器部分的具体内容,是因太冗长容易导致阅读阻力大,很少人愿意认真看(完)。(但即使现在这长度,似乎大家也习惯 tl;dr 了)

希望我有解释清楚。 : )
253 天前
回复了 caobug 创建的主题 Rust RUST 所有权移动问题
FIX: @buxiuxi 第 9 项忘 at 了
253 天前
回复了 caobug 创建的主题 Rust RUST 所有权移动问题
综上

6. 楼主的代码符合 Rust 的所有权规则吗?符合的,因为问题并不在所有权转移上,而是在编译期所有权检查时是否会进行具体求值的判断上。

7. @DianQK 所以**目前**而言不是 bug ,或许以后编译器更聪明效率更高了就支持此优化了。

8. @binhb @qdwang @rrfeng 的说法是对的。

9. 为啥这里 release 函数会释放 dog ?因为 fn release(self)的参数是 self ,跟 std::mem::drop()一样,调用时会获取其所有权,并在此函数结束后 drop 掉。
253 天前
回复了 caobug 创建的主题 Rust RUST 所有权移动问题
续上

3. 对于 Code 3 ,即楼主的第一次修改尝试,实际上等价于将 loop 和 break 去掉(因为此控制流总是执行),所以必然会执行赋值语句,故而编译通过(Ok)。

4. 对于 Code 4 ,即楼主的第二次修改尝试,依然可以将 loop 和 break 去掉,此时情况等价于 Code 0 ,所以一样是编译不通过(Error)。

5. 对于 Code 5 ,即 @Kaiv2 提到的编译成功的写法,因为 break 进去 if 里了,情况就不一样了。此时控制流的逻辑只有两种情况:情况一,若不进入 if 语句里,则无限循环,那么就不会执行后面的使用 person 及 person.dog 的代码了,就没问题;情况二,进入 if 语句,则成功执行赋值,且最后必定 break 出去,执行后面的代码,依然没问题。所以这种不涉及对具体求值有依赖的控制流是能编译通过的。
253 天前
回复了 caobug 创建的主题 Rust RUST 所有权移动问题
回到原问题,person.dog 的各种赋值写法:


0. 对于 Code 0 ,即上一条回复提到的本质问题,此处尽管写着 if true ,然而 Borrow Checker 并不默认此代码总是执行,而是认为可能执行也可能不执行。(当然,当 build release 编译优化阶段时,if true 就会被优化掉了,而所有权检查阶段并不会,或许是太耗时了)

1. 对于 Code 1 ,与 Code 0 本质一样,Borrow Checker 会认为此 for 语句可能不会执行,所以“Rust 的检测器似乎没有认真评估 for 中的条件”确实是对的,并不会在此阶段评估。

2. 对于 Code 2 ,因为有 else 分支且调用 unreachable!(),所以理论上后续使用 person 时必然已经经过 if 部分的赋值语句(因为 else 部分会 panic ,即不会执行后面代码)
253 天前
回复了 caobug 创建的主题 Rust RUST 所有权移动问题
直接简化问题,此问题本质上,与下面代码无法编译通过的原因是一致的:


**即在编译期 Borrow Checker 是不进行具体值计算的。**

如上面的 if 分支,在生命周期检查期间,并不知道 1 == 0 总为 false 且永不执行此代码块。故而在使用 name 时,无法得知所有权是否已被转移。
甚至将 1 == 0 直接换成 false ,此期间 Borrow Checker 依旧不知道 false 总不执行代码块,即认为是**有可能**调用 drop(name)触发所有权转移的。
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2708 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 16ms · UTC 00:18 · PVG 08:18 · LAX 17:18 · JFK 20:18
Developed with CodeLauncher
♥ Do have faith in what you're doing.