[{"content":"Rust 的所有权规则读起来像限制，实际目标却很务实：在编译期确定一块内存由谁释放、谁可以读取、谁可以修改，从而避免悬空指针和数据竞争。\n先看一次移动：\nfn print_name(name: String) { println!(\u0026#34;{name}\u0026#34;); } fn main() { let name = String::from(\u0026#34;Ada\u0026#34;); print_name(name); // println!(\u0026#34;{name}\u0026#34;); // name 已经被移动 } String 管理堆内存。传入函数后，所有权移动给参数，函数结束时内存被释放。若函数只需读取内容，更合适的签名是借用：\nfn print_name(name: \u0026amp;str) { println!(\u0026#34;{name}\u0026#34;); } fn main() { let name = String::from(\u0026#34;Ada\u0026#34;); print_name(\u0026amp;name); println!(\u0026#34;still here: {name}\u0026#34;); } 需要修改时，使用可变借用：\nfn normalize(value: \u0026amp;mut String) { value.make_ascii_lowercase(); } fn main() { let mut language = String::from(\u0026#34;RUST\u0026#34;); normalize(\u0026amp;mut language); println!(\u0026#34;{language}\u0026#34;); } 同一作用域里可以有多个不可变借用，或者一个可变借用，但不能让二者在使用期重叠。这个规则保证“有人修改时，没有其他读者看到半成品”。\n设计函数接口时，我会先问：函数是否需要取得并长期保存这个值？如果是，接收所有权；如果只读，优先接收引用；如果要原地修改，接收可变引用。多数所有权问题，在函数签名表达清楚意图后都会简单很多。\n","permalink":"https://satsukirin.com/posts/rust-ownership-practice/","summary":"把 move、不可变借用和可变借用放进同一个小例子里理解。","title":"从实际代码理解 Rust 所有权与借用"},{"content":"慢 SQL 的优化不该从“再加一个索引”开始，而应该先回答三个问题：数据库预计读取多少行、选择了什么访问方式、排序或临时结果发生在哪里。\n假设有订单表：\ncreate table orders ( id bigint primary key, user_id bigint not null, status varchar(20) not null, created_at timestamp not null ); 常见查询是获取某位用户最近完成的订单：\nselect id, created_at from orders where user_id = 42 and status = \u0026#39;paid\u0026#39; order by created_at desc limit 20; 一个匹配查询形状的联合索引可以是：\ncreate index idx_orders_user_status_created on orders (user_id, status, created_at desc); 索引先放等值过滤字段，再放排序或范围字段。这样数据库可以快速定位 user_id + status 对应的连续区域，并按索引顺序取前 20 条，减少额外排序。\n但索引设计只是猜测，还要用执行计划验证。在 PostgreSQL 中可以运行：\nexplain (analyze, buffers) select id, created_at from orders where user_id = 42 and status = \u0026#39;paid\u0026#39; order by created_at desc limit 20; 重点看实际行数与估算行数是否相差悬殊、是否出现全表扫描，以及排序步骤消耗了多少内存和时间。如果估算明显失真，可能需要更新统计信息，而不一定是索引问题。\n还要警惕对索引列做函数计算。例如 date(created_at) = ... 可能让普通索引难以使用。通常改写成时间范围更直接：\nwhere created_at \u0026gt;= \u0026#39;2026-02-11 00:00:00\u0026#39; and created_at \u0026lt; \u0026#39;2026-02-12 00:00:00\u0026#39; 优化 SQL 的可靠顺序是：复现慢查询、查看执行计划、提出最小改动、再次测量。没有测量的“优化”，往往只是换一种猜法。\n","permalink":"https://satsukirin.com/posts/sql-index-query-plan/","summary":"从一个查询示例出发，判断索引是否可用，并用执行计划验证猜测。","title":"排查慢 SQL：先学会读索引与执行计划"},{"content":"文件、数据库连接、锁和临时目录都有共同点：获取资源后，无论中途是否抛出异常，都必须执行清理。Python 的 with 语句把这个约束写进了代码结构里。\n最常见的例子是文件：\nwith open(\u0026#34;config.json\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as file: content = file.read() 离开代码块时，文件会自动关闭。自定义资源也可以获得同样的行为。对于简单场景，contextlib.contextmanager 比手写类更轻巧：\nfrom contextlib import contextmanager import sqlite3 @contextmanager def open_database(path: str): connection = sqlite3.connect(path) try: yield connection connection.commit() except Exception: connection.rollback() raise finally: connection.close() 使用时，调用方只关心业务操作：\nwith open_database(\u0026#34;app.db\u0026#34;) as db: db.execute( \u0026#34;insert into events(name) values (?)\u0026#34;, (\u0026#34;user_signed_in\u0026#34;,), ) yield 之前相当于进入上下文，yield 之后相当于退出上下文。异常会在 yield 所在位置重新出现，因此可以统一回滚，再用裸 raise 保留原始异常栈。\n一个常见错误是在清理逻辑中吞掉异常。除非确实要把异常转换成可接受结果，否则应该继续抛出，让调用方知道操作没有成功。\n当同一段资源清理代码出现两次时，就值得考虑上下文管理器。它不仅减少重复，更重要的是让“申请—使用—释放”成为一个不可拆散的整体。\n","permalink":"https://satsukirin.com/posts/python-context-manager/","summary":"从 with 语句出发，理解 contextmanager 如何保证资源被可靠释放。","title":"用 Python 上下文管理器收好资源的尾巴"},{"content":"rebase 最适合解决两个问题：让功能分支跟上主分支，以及在合并前整理自己的提交历史。它会重写提交，因此边界要清楚：只整理自己控制的提交，不改写其他人正在依赖的公共历史。\n假设当前在 feature/search 分支，主分支是 main：\ngit fetch origin git rebase origin/main 如果出现冲突，先查看状态：\ngit status 手动修改冲突文件并确认内容正确，然后继续：\ngit add path/to/file git rebase --continue 如果发现方向不对，可以随时回到 rebase 之前：\ngit rebase --abort 在提交较碎时，我会使用交互式 rebase，把“修错字”“补测试”之类的提交合并进对应功能提交：\ngit rebase -i origin/main 编辑器中保留第一个提交为 pick，把后续相关提交改为 fixup。完成后，历史通常会变成几个目的明确、可以独立审查的提交。\n如果这个功能分支已经推送过，rebase 后需要更新远端。使用：\ngit push --force-with-lease 不要随手使用裸 --force。--force-with-lease 会检查远端是否出现了自己尚未看到的新提交，能避免覆盖同事刚推送的工作。\n我的习惯是：开始 rebase 前保证工作区干净；解决冲突后运行测试；推送前再看一遍 git log --oneline --graph。这三个小动作可以省掉大部分返工。\n","permalink":"https://satsukirin.com/posts/git-rebase-workflow/","summary":"用 rebase 整理本地提交，并安全处理冲突与远端分支。","title":"一次干净的 Git Rebase 工作流"},{"content":"在 Go 里，接口值可以理解为一对信息：动态类型和动态值。只有这两部分都为空时，接口才真正等于 nil。\n下面的代码经常让人意外：\npackage main import \u0026#34;fmt\u0026#34; type User struct { Name string } func findUser() *User { return nil } func main() { var result any = findUser() fmt.Println(result == nil) // false } findUser() 返回的是一个类型为 *User 的空指针。它赋值给 any 后，接口的动态类型是 *User，动态值才是 nil。因为动态类型仍然存在，所以整个接口不等于 nil。\n这个问题在返回 error 时尤其危险：\ntype QueryError struct{} func (*QueryError) Error() string { return \u0026#34;query failed\u0026#34; } func query() error { var err *QueryError return err // 返回的 error 不等于 nil } 更稳妥的做法是，在没有错误时直接返回无类型的 nil：\nfunc query() error { // ... return nil } 如果函数必须返回具体指针类型，就在把它装进接口之前判断。排查此类问题时，可以用 %T 和 %v 同时打印类型和值：\nfmt.Printf(\u0026#34;type=%T value=%v\\n\u0026#34;, result, result) 记忆方式很简单：接口不是单纯的“盒子里的值”，它还携带盒子的类型标签。标签没有消失，接口就不算真正为空。\n","permalink":"https://satsukirin.com/posts/go-interface-nil/","summary":"理解 Go 接口的动态类型与动态值，避开 typed nil 带来的误判。","title":"Go 接口里的 nil：为什么看起来为空却不等于 nil"},{"content":"这里是「代码拾光」，一个用于整理编程笔记的小博客。\n文章尽量围绕一个明确问题展开：先解释现象，再给出可以直接运行或复用的解决方案。示例中的代码以清楚、短小为优先，方便日后快速翻阅。\n","permalink":"https://satsukirin.com/about/","summary":"\u003cp\u003e这里是「代码拾光」，一个用于整理编程笔记的小博客。\u003c/p\u003e\n\u003cp\u003e文章尽量围绕一个明确问题展开：先解释现象，再给出可以直接运行或复用的解决方案。示例中的代码以清楚、短小为优先，方便日后快速翻阅。\u003c/p\u003e","title":"关于"}]