RustInPractice TUI(05):多窗口与桌面行为
多窗口 UI 的核心不是同时创建多个窗口,而是让前后层级、点击激活、拖动和关闭都由 Desktop 统一维护。desktop_showcase 用三个重叠的非模态窗口展示这一点。 对应 turbo-vision/desktop_showcase。截图来自实际运行状态,最晚加入的窗口位于最前。 建立层级窗口依次加入 Desktop,后加入的窗口在初始状态位于前方。每个窗口的文本 View 只关心客户区,窗口移动、激活和关闭交给框架。 12345678910app.desktop.add(Box::new(make_window( Rect::new(4, 2, 44, 12), "Window 1 - Background", "Non-modal window.",)));app.desktop.add(Box::new(make_window( Rect::new(32, 4, 74, 15), "Window 3 - Foreground", "Topmost ...
RustInPractice TUI(04):列表、焦点与选择反馈
列表控件把“当前选择”从一串散落的按键判断中抽离出来。ListBox 负责导航和滚动,应用只在收到选择命令时读取选中项并决定下一步。 对应 turbo-vision/list_widgets。截图展示了可用方向键、翻页键和 Enter 操作的初始列表。 数据与选择命令列表由控件持有项目,并在 Enter 确认时发出专用命令。这样事件循环不需要自行维护索引。 1234567const CMD_LIST_SELECT: u16 = 101;let mut listbox = ListBoxBuilder::new() .bounds(Rect::new(4, 3, 36, 16)) .on_select_command(CMD_LIST_SELECT) .build();listbox.set_items(vec!["Rust".into(), "C++".into(), "Python".into()]); 事件顺序绘制时需要同时画 Desktop、ListBox 与状态栏。收到普通键盘事件后...
RustInPractice TUI(03):菜单、状态栏与命令路由
菜单、快捷键和状态栏不应各自执行业务逻辑。它们都应转成统一命令,再由一个边界明确的分发点处理。menu_status 演示了这条链路。 对应 turbo-vision/menu_status。启动时的 About 对话框展示了菜单和状态栏都已绘制。 定义命令内置命令如 CM_NEW、CM_OPEN 和 CM_QUIT 可直接使用;应用自己的命令应避开内置范围。 12345678const CMD_ABOUT: u16 = 100;let help_menu = SubMenu::new( "~H~elp", Menu::from_items(vec![MenuItem::with_shortcut( "~A~bout", CMD_ABOUT, 0, "F1", 0, )]),); 单一分发点事件循环先把 Ctrl-N、Ctrl-O、Alt-X 映射为 Command 事件,再依次交给菜单栏和状态栏处理,最后只在 handle_command 中改变应用状态或打开对话框。 123...
RustInPractice TUI(02):模态表单与 UI 状态
表单的难点不是画两个输入框,而是让输入状态在控件和提交逻辑之间保持一致。dialog_form 用模态 Dialog 暂时接管事件循环,并用共享数据保存输入。 对应 turbo-vision/dialog_form。截图中的姓名和邮箱均为 demo 固定示例数据。 状态归属InputLine 接收 Rc<RefCell<String>>。Rc 允许输入框和提交代码共同持有同一份字符串,RefCell 把借用检查放到运行时。这个组合只适合单线程 UI 状态;跨线程更新应使用消息通道或同步原语。 12345678let name_data = Rc::new(RefCell::new(String::from("Alice")));dialog.add(Box::new( InputLineBuilder::new() .bounds(Rect::new(14, 3, 44, 3)) .max_length(28) .data(Rc::clone(&name_data)) ...
RustInPractice TUI(01):终端窗口与事件循环
终端 UI 不是把 println! 拼成界面,而是维护一组 View,并在绘制、输入和命令之间循环。这个番外使用 turbo-vision 的 Rust 实现,从一个可关闭的窗口开始。 对应 turbo-vision/hello_window。以下截图来自 Docker 中实际运行的 100x30 终端帧。 最小结构Application::new() 初始化终端和 Desktop。窗口由 WindowBuilder 创建,再向其中加入 Label 和 Button。Rect::new(x1, y1, x2, y2) 使用的是两个对角点,不是宽高。 1234567891011let mut app = Application::new()?;let mut window = WindowBuilder::new() .bounds(Rect::new(15, 5, 65, 16)) .title("Hello Turbo Vision") .build();window.add(Box::new(LabelBuilder::new...
RustInPractice(27):动态规划与系列回顾
动态规划把重叠子问题保存为状态表:先定义状态,再写转移,再决定初始化和遍历顺序。它也是 RustInPractice 主线的收官篇。 对应 dynamic_programming。TUI 番外将另行发布。 Fibonacci 只保留前两个状态,将空间从 O(n) 降至 O(1)。0/1 背包一维表必须逆序遍历容量,防止同一物品被重复选择。LCS demo 按 UTF-8 bytes 计算,非 ASCII 的“字符级 LCS”需要不同表示。 当前背包 demo 假设容量非负、weights/values 等长且权重有效;真实 API 应先验证这些输入,避免负数转 usize 或数组越界。 主线至此完成:基础与 Cargo,所有权类型系统,工程并发,最后落到服务、存储和算法。下一步可以继续 TUI 番外,或以一个带测试、配置、数据库和 HTTP 的 capstone 收束实践。 RustInPractice 主线第 27 篇完结。
RustInPractice(26):排序与字符串搜索
排序 demo 用 &mut [i32] 直接操作切片,搜索 demo 用字节序列实现朴素匹配与 KMP。它们适合理解复杂度,生产代码仍应优先标准库。 对应 sorting、string_search。下一篇:动态规划与系列收官。 冒泡和插入排序是 O(n²),快速排序平均 O(n log n),但最后元素 pivot 对已排序输入会退化并导致深递归。默认应使用 sort_unstable(),除非需要稳定排序。 KMP 的 LPS 表让失配后不必回退 haystack,因此复杂度为 O(m+n)。当前实现按 UTF-8 字节搜索,返回字节偏移;非 ASCII 文本不能把该值当字符索引或随意切片。 测试必须覆盖空 slice、单元素、重复值、空 pattern 和 Unicode 边界。 下一篇:动态规划与 RustInPractice 主线回顾。
RustInPractice(25):数据结构与所有权边界
手写数据结构的最大收获不是替代标准库,而是看清所有权如何塑造节点关系。链表和 BST 用 Option<Box<Node>> 表达唯一递归所有权;图用邻接表表达多个边关系。 对应 stack_queue_list、tree、graph、hash_table。 BST 插入的关键是沿着唯一的可变路径递归: 1fn insert(root: &mut Option<Box<TreeNode>>, value: i32) { /* ... */ } 图的 BFS 使用 VecDeque 队列,DFS 用 visited set 防环。自定义 HashMap 使用链地址法展示碰撞,但没有 resize/remove,不能替代标准库。 选择原则:业务优先标准集合;自定义结构只在需求、学习或性能测量证明需要时出现。 下一篇:排序与字符串搜索。
RustInPractice(24):SQLite 与 rusqlite
rusqlite 用 Rust 类型包装 SQLite 的连接、参数绑定、查询和事务。核心规则是:SQL 值永远通过参数绑定传递,不用字符串拼接。 对应 database/sqlite3。下一篇:数据结构。 1234conn.execute( "INSERT INTO students (class_id, name, score) VALUES (?1, ?2, ?3)", params![1, name, score],)?; query_map 将行映射为 struct,? 将数据库失败传播到调用边界。事务应使用 checked transaction,并同时演示 commit 与 rollback;当前 demo 使用内存数据库和 unchecked_transaction(),只适合作为 API 导览。 真实项目还应启用 foreign keys、维护 migration、测试约束、使用持久路径,并决定连接是否只能在同步线程中使用。 下一篇:数据结构在 Rust 中的所有权边界。
RustInPractice(23):WebSocket 与异步流
WebSocket 在 HTTP upgrade 后保持双向消息通道。tokio-tungstenite 将连接同时实现 Stream 与 Sink,接收用 next().await,发送用 send().await。 对应 networking/websocket。下一篇:SQLite。 1234while let Some(message) = ws.next().await { let message = message?; ws.send(Message::Text("Echo: hello".into())).await?;} 服务端通常为每条连接 spawn 一个 task,连接结束后 task 自动结束。需要显式处理 text、binary、ping、pong、close 和错误;demo 将 binary 转成文本提示,因此它是文本 echo,不是保留二进制 payload 的 echo 服务。 accept 循环也不应静默吞掉 listener 错误。生产服务还需要连接上限、超时、认证和关闭协议。 ...










