拓冰建站拓冰建站
首页 / 资讯中心 / 正文

rust-analyzer 架构深度解析:IDE 编译器前端的 crate 分层与设计不变量

开发工具【免费下载链接】rust-analyzerA Rust compiler front-end for IDEs项目地址https://gitcode.com/gh_mirrors/ru/rust-analyzer点击查看免费下载导读本文基于 docs/book/src/contributing/architecture.md 展开系统讲解 rust-analyzer 的高层架构从输入源码 项目结构 → 结构化语义模型的总体数据流到xtask、parser、syntax、base-db、hir-*、ide、rust-analyzer等 crate 的职责划分再到取消机制、测试策略、可观测性等横切关注点。读完本文你将掌握 rust-analyzer 的核心代码地图、各模块之间的 API 边界与架构不变量Architecture Invariant并能在阅读源码时快速定位某个功能由哪个 crate 负责、在哪里实现。rust-analyzer 本质上是一个为 IDE 设计的 Rust 编译器前端它不需要生成机器码而是把源码解析、名称解析、宏展开、类型推断等编译步骤改造成可增量、可取消、常驻内存的服务为补全、跳转、重命名、诊断等 IDE 功能提供语义基础。全景视角输入与输出从最高层看rust-analyzer 是一个这样的系统接收客户端提交的源代码产出一份结构化的代码语义模型。具体来说输入数据ground state即基础状态由两部分组成一组测试文件以(PathBuf, String)二元组表示的文件集合项目结构信息由CrateGraph承载它指明哪些文件是 crate 根、每个 crate 指定了哪些cfg标志、crate 之间存在怎样的依赖关系。分析器会把全部输入数据常驻内存全程不做任何 IO。由于输入本质上是源码文本通常最多几十 MB全部放进内存是可行的。而结构化语义模型derived state派生状态则是源码中出现的模块、函数、类型的面向对象式表示。这种表示是**完全解析resolved**的所有表达式都有类型、所有引用都绑定到了声明上。客户端只需提交一小份输入数据的增量典型场景是某个文件的一处修改即可得到反映该变化的全新代码模型。底层引擎保证模型是惰性按需计算的并能针对小改动快速更新——这正是基于 salsa 增量计算数据库的设计初衷详见后文base-db一节。入口点从 main 函数到 LSP 请求处理理解一个大型代码库从入口开始是最直接的路径crates/rust-analyzer/src/bin/main.rs 中的main函数负责启动 LSP 服务器。它是唯一的程序入口但前置了大量复杂性参数解析、日志初始化、根据 CLI 子命令分流到 LSP 服务器或批处理分析因此快速浏览即可。crates/rust-analyzer/src/handlers/request.rs 实现了全部 LSP 请求。如果你已经熟悉 LSP这是最佳的阅读起点。Analysis与AnalysisHost类型定义了面向 IDE 服务使用者的主 API。在 crates/rust-analyzer/Cargo.toml 中可以确认二进制定义[[bin]] name rust-analyzer path src/bin/main.rs入口代码还展示了一个进程、两种模式的设计当环境变量RA_RUSTC_WRAPPER被设置时main会转入rustc_wrapper::main()否则进入正常的actual_main()见 crates/rust-analyzer/src/bin/main.rs后者解析flags::RustAnalyzer后根据子命令分支。代码地图逐 crate 解析以下各小节对应 docs/book/src/contributing/architecture.md 中的 Code Map。请特别留意各节标注的Architecture Invariant架构不变量——它们往往描述源码中刻意不存在的东西是理解设计取舍的关键。同时注意哪些 crate 是API BoundaryAPI 边界。xtaskrust-analyzer 的构建系统xtask是 rust-analyzer 自建的构建系统目录。Cargo 负责编译 Rust 代码但仓库里还有大量其他任务——发布管理、本地安装、文档/语法树代码生成等它们统一由xtask目录下的 Rust 代码处理。相关入口见 xtask/src/main.rs。editors/codeVS Code 插件这是 rust-analyzer 官方维护的 VS Code 客户端插件负责与 LSP 服务器通信、渲染补全/诊断/语法高亮等。它的 TypeScript 源码位于 editors/code/src与服务器端的 Rust 代码严格隔离属于 LSP 协议两侧的独立实现。lib/独立发布到 crates.io 的库lib/目录存放与 rust-analyzer 相对独立的库如la-arena、line-index、lsp-server、smol_str、text-size、ungrammar等。它们遵循各自独立的 semver 版本见 Cargo.toml目前未被重度使用意味着 rust-analyzer 主体尽量只依赖这些轻量、稳定的基础设施。crates/parser手写递归下降解析器parser是手写的递归下降解析器。它不直接产出语法树而是产出一串事件events例如开始节点 X结束节点 Y。事件流的具体定义见 crates/parser/src/event.rsEvent枚举包含Start { kind, forward_parent }开启一个节点可用forward_parent处理左递归文档中给出了foo::bar路径的事件序列示例Finish结束最近的StartToken { kind, n_raw_tokens }产出叶子元素n_raw_tokens用于把词法器切分的多个 token如拆成两个重新粘合FloatSplitHack { ends_in_dot }处理foo.0.0这类词法上下文歧义Error { err }指向 parser 侧表errors中的一条错误消息保证每个Event只有 8 字节。TreeSink与TokenSource两个 trait 负责把树无关、token 无关的解析器与rowan语法树桥接起来见 crates/parser/src/lib.rs。架构不变量解析器解析器与具体树结构、具体 token 表示无关它只做一个扁平事件流 → 另一个扁平事件流的转换。token 无关性让同一套解析逻辑既能解析文本源码、也能解析tt形式的宏输入树无关性则便于更换语法树实现并为轻量解析例如不建树就能提取文件中定义的名字用于拼写纠正留出空间。解析永不失败解析器返回(T, VecError)而非ResultT, Error。crates/syntax基于 rowan 的语法树syntaxcrate 定义 Rust 语法树结构与解析器使用 rowanast模块在原始 rowan 树之上提供类型安全的 APIungrammar是语法的形式化描述crates/syntax/rust.ungram用于生成syntax_kind与ast模块生成命令是cargo test -p xtask。架构不变量syntaxsyntaxcrate 与 rust-analyzer 其余部分完全独立它不知道 salsa 或 LSP 的存在。正因为只靠语法树就能做出有用的工具无需语义信息就不必能构建代码工具更健壮syntax被视为 rust-analyzer 的一个入口点与API Boundary。语法树是值类型树完全由语法节点的内容决定不需要全局上下文如 interner也不存语义信息。IDE 场景中assist 与重构需要变换语法树若把语义信息塞进树里会很别扭。语法树按单文件构建这是为了支持所有文件的并行解析。语法树刻意不完整、不强制良构AST 方法返回Option时运行时确实可能为None即使语法上不允许。测试方面syntax的测试主要是数据驱动的test_data/parser目录存放.rs测试向量与对应的.txt语法树期望文件测试时用.rs对.txt.txt缺失时自动创建这也是更新测试的方式。此外运行cargo test -p xtask会把grammar模块中所有// test test_name注释收集成test_data/parser/inline目录下的文件。更新测试数据使用env UPDATE_EXPECT1 cargo qt新增一条 inline 测试后需要运行cargo test -p xtask并按上述方式更新测试数据。crates/base-dbsalsa 与输入查询rust-analyzer 使用 salsa。粗略地讲可以把 salsa 想象成一个键值存储但它还能用指定函数计算派生值。base-dbcrate 提供与 salsa 交互的基础设施并定义了大多数输入查询——即由分析器客户端提供的事实。阅读 crates/base-db/src/input.rs 的文档很有价值其他一切内容都严格从这些输入派生。该模块的自述也强调在某种意义上这是最重要的模块因为其他所有花哨的东西都严格由这份输入推导而来且分析器核心不做真实 IO。架构不变量base-db构建系统的细节不属于ground statebase-db完全不知道 cargo。例如cfg标志属于base-db而feature不属于——foofeature 是 Cargo 层面的概念由 Cargo 降级为命令行参数--cfg featurefoo。crate 间的依赖用CrateGraph结构抽象表示。base-db不知道文件系统与文件路径文件用不透明的FileId表示没有任何操作能从FileId换回std::path::Path。真实 IO 由vfs与project-model完成并降级为输入见 crates/base-db/src/input.rs。hir-expand/hir-def/hir-tyIDE 的大脑这三个 crate 是 rust-analyzer 的大脑也就是IDE 中的编译器部分。名称解析、宏展开、类型推断都发生在这里同时它们定义核心的各种中间表示。它们带有强烈的 ECSEntity Component System风格直接用原始 id 干活、直接查询数据库几乎没有抽象层与 salsa 和 chalk 深度集成。几个核心数据结构ItemTree把单个SyntaxTree压缩成摘要结构对函数体修改保持稳定。定义在 crates/hir-def/src/item_tree.rs对应的计算入口item_tree查询位于同文件 L123 附近。DefMap包含 crate 的模块树并保存各模块的作用域。定义于 crates/hir-def/src/nameres.rscrate_def_map查询在其 L380。Body存储关于表达式的信息。架构不变量hir-三件套*这些 crate不是、也永远不会是API 边界。它们明确关注增量性核心不变量是在函数体内打字永不使全局派生数据失效——即修改foo的函数体关于bar的一切事实都应保持不变。hir 只存在于特定 crate 实例 特定 CFG 标志的语境中同一个语法若 crate 在 crate 图中出现多次可能产生多份 hir 实例。crates/hir面向外部的门面顶层的hircrate 是API Boundary。若考虑把 rust-analyzer 当库用hir就是最可能面向你的门面它把 ECS 风格的内部 API 包装成面向对象风格每次调用额外带一个db参数。架构不变量hirhir提供静态、完全解析的代码视图——内部hir-*crate计算东西而从外部看hir就像一块惰性数据结构。hir还处理从语法到对应 hir这一精细任务注意这里的映射是一对多的参见Semantics类型与source_to_def模块分别位于 crates/hir/src/semantics.rs 与 crates/hir/src/semantics/source_to_def.rs。其中存在一个有趣的递归结构先把父级syntax节点解析为父级hir元素再问该hir父元素拥有哪些syntax子节点最后在子节点集合中寻找我们的节点。这正是 goto definition 等大量 IDE 功能的核心——它们都始于弄清光标处的 hir 节点。这种模式在 Roslyn 与 Kotlin 中同样存在是一种尚未命名的超级 IDE 模式。ide系列高层 IDE 功能idecrate 在hir语义模型之上构建补全、goto definition 等高层次 IDE 功能同样是API Boundary。无论你想通过 LSP、自定义 flatbuffers 协议、还是作为库在文本编辑器中使用 rust-analyzer 的 IDE 部分ide都是正确的 API 层。架构不变量ideide的 API 由带公开字段的 PODPlain Old Data类型组成它使用编辑器的术语谈论偏移量与字符串标签而不是定义与类型。它相当于 MVC 中的 View / MVVM 中的 ViewModel。所有参数与返回类型在概念上都是可序列化的——语法树与 hir 类型一般不出现在 API 中但实现里大量使用。ide还是第一个具有随时间变化概念的 crateAnalysisHost是可事务性apply_change的状态Analysis是该状态的不可变快照。在 crates/ide/src/lib.rs 可以看到这两个类型的实际定义AnalysisHost内部持有RootDatabase通过analysis()返回快照apply_change应用变更而Analysis的所有现有实例在 world state 前进后会被取消大多方法返回Err(Canceled)。注意Analysis的 API 设计原则是独立于语言服务器协议——即使目前 LSP 是唯一消费者该 API 在理论上也应可作库使用或经由别的协议调用。在ide内部又拆分为多个 crateide-assists、ide-completion、ide-diagnostics、ide-ssr实现大型独立功能ide-db实现通用 IDE 功能最值得注意的是引用搜索在这里实现ide本体包含公共 API/门面以及大量小型功能的实现。架构不变量ide 的完美 API 追求ide努力提供完美的 API。尽管目前它只有一个消费者LSP 服务器但 LSP不影响其 API 设计设计时心里始终有一个假想的理想客户端——一个专门为 Rust 量身定做的 IDE。crates/rust-analyzerLSP 服务器本体这个 crate 定义rust-analyzer二进制入口点实现语言服务器。架构不变量rust-analyzer它是唯一知道 LSP 与 JSON 序列化的 crate。若想把ide中的数据结构X暴露给 LSP不要给它加序列化而是在rust-analyzercrate 里创建一个可序列化的对应物并手动转换。GlobalState是服务器状态main_loop定义服务器事件循环——接收请求、发送响应。修改状态或可能阻塞用户输入的请求在主线程处理其余请求在后台处理。事件循环的封闭性在 crates/rust-analyzer/src/main_loop.rs 中体现得很明显它接收一个enum EventLsp、Task、DeferredTask、Vfs、Flycheck、TestResult、DiscoverProject、FetchWorkspaces而不是到处 spawn future 或调度回调。架构不变量无状态服务器服务器是无状态的类似 HTTP。有时请求之间需要保留状态例如上一个补全列表第 5 个补全项的edit是什么——为此第二个请求必须携带足够的信息从零重建上下文通常意味着包含原请求的全部参数。reload模块处理配置与Cargo.toml变化的逻辑这是一件棘手的事。架构不变量部分可用即使构建是坏的rust-analyzer也应保持部分可用——reload 过程不应妨碍 IDE 功能工作。与 cargo 交互的 cratetoolchain、project-model、flycheck这些 crate 负责调用cargo来了解项目结构、以及为保存时检查check on save功能获取编译器错误。它们大量使用crates/paths而非std::path——因为单个 rust-analyzer 进程可以同时服务多个项目服务器当前目录绝不能泄漏到路径解析中。宏相关 cratembe、tt、proc-macro-api、proc-macro-srv、proc-macro-srv-cli这些 crate 把宏实现为token tree → token tree的变换与其余代码相互独立ttcrate 定义TokenTree——单个 token 或带定界的 token tree 序列mbecrate 提供语法树与 token tree 之间互转的工具并处理声明式宏Macros By Example的实际解析与展开过程宏采用客户端-服务器模型启动独立进程proc-macro-srv-cli加载并运行过程宏客户端proc-macro-api提供与服务器对话的接口。token tree 从客户端传入服务器加载由cargo构建的对应动态库。由于 rustc 获取过程宏结果的 API 长期不稳定项目自己维护了一份该部分代码的拷贝从而可以在 stable Rust 上构建整个项目。架构不变量宏糟糕的过程宏可能 panic 甚至段错误所以要放进独立进程运行并从致命错误中恢复它们还可能非确定这与 salsa 的工作方式冲突需要特别注意。基础设施类 cratecrates/cfg负责cfg属性的解析、求值与一般定义。crates/vfs、crates/vfs-notify、crates/paths实现虚拟文件系统提供底层文件系统的一致性快照并隔离混乱的 OS 路径。架构不变量vfs 不假设单一统一文件系统——单个 rust-analyzer 进程可以充当两台不同机器的远程服务器此时相同的/tmp/foo.rs路径指向不同文件因此所有路径 API 通常要求传入某个既有路径作为文件系统见证witness。crates/stdx存放各类非 rust-analyzer 专属的工具函数本可以进 std以及希望尽早使用的 unstable std 条目拷贝。crates/profileCPU 与内存剖析工具。crates/intern通过Arc做全局字符串/符号驻留的基础设施。crates/load-cargo暴露若干项目加载工具供主rust-analyzercrate 与其他下游消费者使用。crates/rustc-dependencies包装 rust-analyzer 依赖的rustc_*crates并有条件地把它们指向 crates-io 的镜像版本使项目能在 stable 上继续构建。crates/span暴露宏相关 span 的类型与函数——一个 span 本质上就是某个文件内、相对某条目、带给定SyntaxContexthygiene的文本区间。横切关注点无处不在又无处安放的设计原文档专门用一章讨论处处都在、又不在任何具体地方的横切关注点这里逐条展开。稳定性保证rust-analyzer 之所以能快速演进一个原因是不引入新的稳定性承诺而是尽可能借用现有的稳定性保证ideAPI 明确不稳定但LSP 接口是稳定的——这里实现的是一份由他人维护的稳定 APIRust 语言与 Cargo 是稳定的且它们是 rust-analyzer 的主要输入rowan发布到 crates.io但刻意保持在1.0以下总是做 semver 不兼容升级。另一个重要例子rust-analyzer 并不在 CI 上运行指作为语言服务器运行因此与rustc、clippy不同改动其运行时行为是允许的。未来可能会考虑开放 API 或允许 crates.io 库携带 rust-analyzer 专属注解但那将是巨大的承诺。例外情况rust-project.json对非 cargo 构建系统而言是事实上稳定的格式。它够用但属于隐式稳定——教训是设计可能成为稳定性边界的 API 时别等到有第一个用户才去稳定它用户会先使用、然后才通知你你有用户了。项目也发布一些LSP 扩展并尽量让它们保持稳定这里的受众是有限的编辑器维护者因此不提供铁一般的保证也能运转。代码生成仓库中部分组件由自动化流程生成生成代码在cargo test时自动更新并且一般会提交进 git 仓库。具体生成内容包括语法树操作 APIsyntax::ast由ungrammarcrate 驱动。实际生成逻辑见 xtask/src/codegen/grammar.rs读取 crates/syntax/rust.ungram经generate_kind_src/generate_syntax_kinds/generate_tokens/generate_nodes产出crates/parser/src/syntax_kind/generated.rs、crates/syntax/src/ast/generated/tokens.rs、crates/syntax/src/ast/generated/nodes.rs等文件手册的多个章节features、assists、configassists 的文档测试细节见xtask\src\codegen\assists_doc_tests.rs模块即 xtask/src/codegen/assists_doc_tests.rs。架构不变量避免 bootstrap代码生成需要解析 Rust 代码但刻意不用 rust-analyzer 自己来做那会很有趣但会严重复杂化构建流程而是使用syn与手工字符串解析。取消机制设想 IDE 正在计算语法高亮用户突然敲了foo应该发生什么rust-analyzer 的回答是高亮计算应当被取消——其结果已过期而且它还阻塞着输入修改。机制上salsa 数据库维护一个全局 revision 计数器。应用变更时 salsa 递增计数器并等待所有正在使用 salsa 的线程结束若某线程正在做 salsa 计算并发现计数器已递增它会抛出一个特殊值 panic见Canceled::throw。也就是说rust-analyzer 依赖 unwinding。ide是捕获该 panic 并转换成ResultT, Cancelled的边界。AnalysisHost::apply_change的文档也印证了这一点如果有未完成的快照它们将被取消crates/ide/src/lib.rs。测试策略rust-analyzer 把测试集中在三条有趣的系统边界上最外层边界是rust-analyzercrate它以 stdio 定义 LSP 接口通过喂入一串 LSP 请求、检查响应做集成测试。这类测试被称为heavy测试因为它们会与 Cargo 交互、从磁盘读取真实文件只有在设置RUN_SLOW_TESTS环境变量时才运行。由于消息本身有类型在静态类型语言里很难在协议层犯错所以尽量少写该边界的测试。中间、也是最重要的边界是ide典型测试创建一个AnalysisHost、调用若干Analysis函数并与期望结果对比。最内层、最精细的边界是hir它的类型词汇比ide丰富得多但基本测试方式相同——建数据库、跑查询、断言结果。比较使用expectcrate 做快照测试各种分析边界情况与旧测试的记忆用markscov_markcrate管理。架构不变量测试rust-analyzer 的测试不使用 libcore 或 libstd所有必要库代码必须作为测试的一部分——这保证测试执行迅速。测试是数据驱动的不直接测试 API直接调用各种 API 函数的测试是负债会显著复杂化 API 重构。大多数测试长这样#[track_caller] fn check(input: str, expect: expect_test::Expect) { // The single place that actually exercises a particular API } #[test] fn foo() { check(foo, expect![[bar]]); } #[test] fn spam() { check(spam, expect![[eggs]]); } // ...还有一百多个完全不在乎具体 API 的测试输入数据用单一字符串字面量以特殊格式描述一组 Rust 文件即Fixture格式带//-注释元数据可声明文件路径、crate 名、依赖、cfg、env等见 crates/test-utils/src/fixture.rs。所有代码不变量都由#[test]测试覆盖CI 中没有额外检查——格式与 tidy 测试都通过cargo test运行。测试不依赖任何外部资源完全可复现。性能测试原文档标注为 TBA待补充并指引查看metricsxtask 与#[test] fn benchmark_xxx()函数。仓库中已有实测基准数据目录 bench_data如glorious_old_parser、numerous_macro_rules可作为了解性能测试形态的参考。错误处理架构不变量错误处理rust-analyzer 的核心部分ide/hir不与外部世界交互因此不会失败只有接触 LSP 的部分才允许做 IO。内部处理坏代码不是错误条件——rust-analyzer 是健壮的各种分析返回(T, VecError)而非ResultT, Error。同时它是复杂的常驻进程永远会有 bug 与 panic但孤立功能中的 panic 不应拖垮整个进程每个 LSP 请求都由catch_unwind保护用always/never宏代替assert从不可能的条件中优雅恢复。这一点在 crates/rust-analyzer/src/global_state.rs 中也有印证如AssertUnwindSafe的使用与 server panicked 消息处理。可观测性rust-analyzer 是常驻进程理解其内部状况至关重要为此配备了多种工具事件循环非常显式不 spawn future、不调度回调开放而是接收enum事件封闭见上文main_loop。很容易看清所有触发处理的事件及其性能。分层剖析器hprof用RA_PROFILE*50环境变量启用记录所有耗时超过 50ms 的动作输出形如85ms - handle_completion 68ms - import_on_the_fly 67ms - import_assets::search_for_relative_paths 0ms - crate_def_map:wait (804 calls) 0ms - find_path (16 calls) 2ms - find_similar_imports (1 calls) 0ms - generic_params_query (334 calls) 59ms - trait_solve_query (186 calls) 0ms - Semantics::analyze_impl (1 calls) 1ms - render_resolution (8 calls) 0ms - Semantics::analyze_impl (5 calls)这套机制成本足够低可以放到生产环境。过滤器语法还有更多形式见 crates/rust-analyzer/src/tracing/config.rsenv RA_PROFILE* // 输出全部 env RA_PROFILEfoo|bar|baz // 只启用选中的条目 env RA_PROFILE*310 // 输出全部深度最多 3只显示耗时超过 10ms 的存活对象计数RA_COUNT1保存存活对象数量统计。它目前成本太高、不适合生产开启这被认为是一个应当修复的 bug。可配置性rust-analyzer 追求尽可能可配置同时在尚无配置之处提供合理默认值。经验法则是默认开启大多数功能除非它们有 bug 或过度拖累性能。默认开启是可发现性问题——许多用户即使手册里写了也不知道某些功能存在。但文档也坦承存在代码—架构差距目前使用的 feature 开关比实际应该用的要少。序列化在 Rust 中给类型加#[derive(Serialize)]很容易常常太容易但这种便利具有误导性——可序列化类型意味着沉重的向后兼容约束一旦类型可序列化它就属于某个 IPC 边界的一部分而你往往无法控制该边界另一端改动会很难。因此ide、base_db及以下的类型按设计不可序列化。若这些类型需要跨 IPC 边界rust-analyzer 的客户端必须提供自定义的、客户端专属的序列化格式把向后兼容与迁移的顾虑隔离到特定客户端。例如rust-project.json是自己的格式——它不直接包含CrateGraph而是调用相应构造函数来创建CrateGraph。结语如何顺着本文深入源码rust-analyzer 的架构本质上是以 salsa 增量数据库为地基、以syntax → hir-* → hir → ide → rust-analyzer(LSP)为主干的分层管道每一层都用架构不变量明确划定了职责与边界parser 永不失败、syntax 自给自足、base-db 不知文件系统、hir 三件套只为增量而生、ide 提供 POD 化的完美 API、rust-analyzer 独占 LSP。建议的阅读路线先跑一遍cargo test -p xtask感受代码生成与测试闭环再读 crates/base-db/src/input.rs 建立输入即一切的心智模型然后按parser → syntax → hir-def → hir-ty → hir → ide → rust-analyzer的顺序顺流而下最后用RA_PROFILE观察一次真实的补全请求是如何被层层分解的。配合仓库中的 docs/book/src/contributing/guide.md较早期的开发指南与 docs/book/src/contributing/syntax.md语法树设计笔记你可以更快地把这份架构地图变成自己的导航。赞分享开发工具【免费下载链接】rust-analyzerA Rust compiler front-end for IDEs项目地址https://gitcode.com/gh_mirrors/ru/rust-analyzer点击查看免费下载相关推荐Anki 架构深度解析Rust 核心、Python 桥接与 Qt/TypeScript 前端的分层设计Anki 架构深度解析Rust 核心、Python 桥接与 Qt/TypeScript 前端的分层设计 Anki 是知名的开源间隔重复spaced repe教育FastAPI WebSocket掌握连接状态管理的完整指南FastAPI WebSocket掌握连接状态管理的完整指南 FastAPI 作为一款高性能、易学习的现代 Python Web 框架提供了强大的 WebS后端Web框架API设计Trippy crate 架构全解析六个 Rust 库的分层设计与集成指南Trippy crate 架构全解析六个 Rust 库的分层设计与集成指南 Trippy 是一个用 Rust 编写的网络诊断工具集 traceroute、p网络CLI运维上一篇BoxMOT项目基于相机运动补偿的海上大型海洋生物航拍追踪技术解析下一篇如何快速安装Thorium-Win新手5分钟上手教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门