第 3 章 第一个 Mojo 程序与 CLI 全解

第 3 章 第一个 Mojo 程序与 CLI 全解

本章目标:把一个 Mojo 程序从源码走到可执行文件、汇编、LLVM IR、共享库;把 mojo 的 8 个子命令逐个用一遍;最后写一个真正有用的命令行工具(wc)。本章所有输出都是在 Linux x86-64 + Mojo 1.2.0 上的真实终端记录。


3.1 程序的结构:main() 与顶层声明

每个 Mojo 可执行程序都必须有一个名为 main() 的函数作为入口:

mojo 复制代码
def main():
    print("Hello, world!")
output 复制代码
Hello, world!

注意几件事:

  1. main() 不需要 raises ,除非它内部调用了可能抛错的函数且不打算自己处理(见第 10 章)。print() 本身不是 raising 函数。
  2. 顶层不能有可执行语句。这是很多 Python 转 Mojo 的人第一个撞上的墙:
mojo 复制代码
var x = 10          # 错误:global variables are not supported
print("hi")         # 错误:expressions must not appear at file scope

编译器会明确告诉你:

output 复制代码
error: global variables are not supported; move this into a function body
       or use 'comptime' to declare a constant

顶层(文件作用域)只允许声明 :def、struct、trait、import、from ... import,以及 comptime 常量。

  1. 顶层常量用 comptime:
mojo 复制代码
comptime MAX_SIZE = 1024
comptime Greeting = "hello"
comptime Vec8 = SIMD[DType.float32, 8]

def main():
    print(MAX_SIZE, Greeting)
    var v = Vec8(1.0)
    print(v.reduce_add())
output 复制代码
1024 hello
8.0

对比 Python :Python 的模块顶层可以跑任意代码(这就是为什么 import 有副作用)。Mojo 的顶层是纯声明式的------这让编译期分析、增量编译、AOT 优化都成为可能。需要"模块加载时执行"的逻辑?放到 main() 或显式的 init() 函数里。


3.2 mojo run:最快的迭代循环

bash 复制代码
mojo run hello.mojo      # 显式
mojo hello.mojo          # 等价简写

run = 编译到临时产物 + 立即执行。它是开发期的主力命令。

关键选项

选项 说明
-O0 / -O1 / -O2 / -O3 优化级别,默认 3 ;--no-optimization ≡ -O0
-I <PATH> 追加模块搜索路径(可多次)
-D <KEY=VALUE> 定义编译期常量,源码里用 std.sys.is_defined 查询
-g / -g0/-g1/-g2 调试信息级别:none / line-tables / full
-j <N> 编译并行线程数(默认 0 = 全部核心)
--emit <KIND> 产出别的东西而不是可执行文件(见 3.4)

开发期技巧 :编译大型泛型代码时 -O3 可能很慢。先用 -O0 或 -O1 快速验证逻辑,最后再 -O3 测性能。这是 Mojo 版的"debug build / release build"。

参数传递的顺序陷阱

bash 复制代码
mojo run -O2 app.mojo --port 8080 --verbose
#        └── 给编译器 ──┘└──── 给 app 程序 ────┘

**文件路径之前的选项给 mojo,之后的原样传给你的程序。**在程序里这样读:

mojo 复制代码
import std.sys

def main() raises:
    var args = std.sys.argv()
    print("argc =", len(args))
    for i in range(len(args)):
        print(t"  argv[{i}] = {args[i]}")
bash 复制代码
mojo run args.mojo --port 8080 --verbose
output 复制代码
argc = 3
  argv[0] = --port
  argv[1] = 8080
  argv[2] = --verbose

⚠️ 注意:mojo run 时 argv[0] 是第一个程序参数 ,不是可执行文件名(与 mojo build 出的二进制不同,后者的 argv[0] 是程序路径)。写健壮的 CLI 时不要依赖 argv[0] 的含义,从索引 0 开始当作纯参数列表处理,或用 mojo build 后的二进制。


3.3 实战:写一个 wc(行数/词数/字节数统计)

这是一个麻雀虽小五脏俱全的 CLI 程序:读命令行参数、打开文件、遍历 UTF-8 码点、格式化输出、支持编译期开关。

mojo 复制代码
import std.sys
from std.io.file import open
from std.sys import is_defined

def pad_left(s: String, width: Int) -> String:
    var out = String()
    var n = width - Int(s.count_codepoints())
    for _ in range(max(n, 0)):
        out += " "
    out += s
    return out

def count_file(path: StringSpan) raises -> Tuple[Int, Int, Int]:
    var lines = 0
    var words = 0
    var chars = 0
    with open(path, "r") as f:
        var text = f.read()
        chars = text.byte_length()
        var in_word = False
        for cp in text.codepoints():
            var c = Int(cp)
            if c == 10:
                lines += 1
            if c == 32 or c == 10 or c == 9:
                in_word = False
            elif not in_word:
                in_word = True
                words += 1
    return lines, words, chars

def main() raises:
    comptime if is_defined["VERBOSE"]():
        print("[verbose build]")
    var args = std.sys.argv()
    if len(args) < 2:
        print("usage: wc <file> [<file> ...]")
        return
    var tl = 0
    var tw = 0
    var tc = 0
    for i in range(1, len(args)):
        var l, w, c = count_file(args[i])
        print(pad_left(String(l), 7), pad_left(String(w), 7),
              pad_left(String(c), 7), args[i])
        tl += l
        tw += w
        tc += c
    if len(args) > 2:
        print(pad_left(String(tl), 7), pad_left(String(tw), 7),
              pad_left(String(tc), 7), "total")

保存为 wc.mojo,准备两个测试文件后运行:

bash 复制代码
printf 'hello world\nthis is a test\nthird line here\n' > sample.txt
printf 'a b c d e\nf g\n' > sample2.txt
mojo run wc.mojo sample.txt sample2.txt

真实输出:

output 复制代码
      3       9      43 sample.txt
      2       7      14 sample2.txt
      5      16      57 total

这段 40 行的程序里已经用到了本书后面会详细讲的六个特性:

代码 特性 章节
with open(...) as f 上下文管理器 / RAII 第 10 章
raises / main() raises 错误传播 第 10 章
StringSpan 非拥有字符串视图 第 23 章
text.codepoints() UTF-8 感知的迭代 第 23 章
Tuple[Int, Int, Int] + 解包 多返回值 第 5 章
comptime if is_defined[...] 编译期开关 第 18 章、26 章

一个刻意的"坑":为什么不用 t"{l:>7}"?

Python 程序员会本能地写 f"{lines:>7}"。在 Mojo 里这会编译失败:

mojo 复制代码
def main():
    var l = 42
    print(t"{l:>7}")
output 复制代码
error: format specifiers are not supported in t-strings;
       format the value manually before interpolating

TString(模板字符串)不支持格式说明符 。同样,String.format() 也只支持位置索引,不支持 : 后的对齐/宽度/精度:

mojo 复制代码
def main():
    print("{:>7}".format(42))
output 复制代码
note: constraint failed: Index :<7 not in kwargs

format() 支持的只有这两种:

mojo 复制代码
def main():
    print("{0} {1} {0}".format("Mojo", 1.125))
    print("{} {}".format(True, "hello"))
output 复制代码
Mojo 1.125 Mojo
True hello

所以本书的 wc 里手写了 pad_left()。这是 Mojo 当前的真实状态:字符串格式化能力弱于 Python,需要自己补。第 23 章会给出一套可复用的格式化辅助函数。


3.4 mojo build:产出可交付物

bash 复制代码
mojo build -O3 -o wc wc.mojo
./wc sample.txt

实测:

output 复制代码
$ mojo build -O3 -o wc wc.mojo
$ ls -la wc
-rwxr-xr-x 1 user user 75752 ... wc
$ ./wc sample.txt
      3       9      43 sample.txt

75 KB 的静态可执行文件,无 Python 依赖(除非你用了 Python 互操作------那么运行环境必须提供相应的 Python 库,官方文档明确提醒:"Beware that any Python libraries used in your Mojo project are not included in the executable binary")。

--emit:不产出可执行文件,产出中间产物

mojo build --emit <KIND> 是理解 Mojo 编译流水线的最佳窗口。实测支持的类型(括号内是支持该类型的目标):

--emit 产物 支持目标
exe(默认) 可执行文件 amdgpu, host, metal, nvptx
asm 目标汇编;GPU 目标还会为每个 kernel 产出 sidecar 文件 amdgpu, host, metal, nvptx
llvm 未优化的 LLVM IR;GPU 同样有 sidecar amdgpu, host, metal, nvptx
llvm-bitcode LLVM bitcode (.bc) amdgpu, host, metal, nvptx
object 单个目标文件(实验性) amdgpu, host, metal, nvptx
shared-lib 动态库 amdgpu, host, metal, nvptx
air Apple AIR bitcode(每 kernel 一个)+ host LLVM bitcode metal

试试:

bash 复制代码
mojo build --emit asm -O3 wc.mojo -o wc.s
mojo build --emit llvm -O1 wc.mojo -o wc.ll

实测 wc.s 约 330 KB(含全部内联展开),wc.ll 约 23,781 行。看一眼 LLVM IR 的开头:

output 复制代码
; ModuleID = 'wc.mojo'
source_filename = "wc.mojo"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

@static_string_a8d4ace0dc8d360e = internal constant [2 x i8] c" \00", align 16

这是排查性能问题的第一手材料 :第 31 章会教你用 --emit asm 检查热点循环是否真的向量化了。

--emit shared-lib:给别的语言用

mojo 复制代码
@export("add_one")
def add_one(x: Int) abi("C") -> Int:
    return x + 1

注意 abi("C"):@export 要求函数显式声明 C ABI 效果,否则只是警告,但符号调用约定可能不对。

bash 复制代码
mojo build --emit shared-lib -O3 lib1.mojo -o libmylib.so
nm -D --defined-only libmylib.so | head

实测输出:

output 复制代码
0000000000001100 T add_one

一个干净的 C ABI 符号,可以被 C/C++/Python(ctypes)/Rust 直接调用。第 29 章详解。

目标平台选项(交叉编译)

bash 复制代码
mojo build --print-effective-target        # 当前会用哪个目标
mojo build --print-supported-targets       # 支持哪些目标三元组
mojo build --print-supported-cpus          # 支持哪些 CPU
mojo build --print-supported-accelerators  # 支持哪些加速器

实测 --print-effective-target:

output 复制代码
Effective target configuration:
  --target-triple x86_64-unknown-linux-gnu
  --target-cpu cascadelake

--print-supported-accelerators 的开头:

output 复制代码
Supported Accelerator Architectures:

#
AMD (ROCm/HIP):
  gfx90a         - CDNA2 (MI250X)
  ...

相关选项:--target-triple、--target-cpu、--target-features、--target-abi、--march、--mcpu、--mtune、--target-accelerator。第 26 章讲交叉编译工作流,第 32 章讲 GPU 目标。

浮点行为控制:--fp-mode

bash 复制代码
mojo build --fp-mode contract=off -O3 numerics.mojo

--fp-mode 接受 feature=value 列表(可多次给出),目前只有一个 feature:contract。

  • contract=fast(默认 ):类似 Clang 的 -ffp-contract=fast,会把 a + b*c 融合成 FMA,跨语句也融合,因此破坏严格 IEEE 一致性;
  • contract=off:关闭收缩。

这在数值代码里非常重要 :默认开启的 FMA 收缩会让你的结果和"逐运算舍入"的参考实现有细微差异。做精度对齐(比如把 Mojo kernel 的结果和 PyTorch 逐位比对)时,先试 contract=off。第 31、34 章会反复用到。

其他值得知道的 build 选项

选项 用途
-D KEY=VALUE 编译期常量(配合 std.sys.is_defined / get_define)
--sanitize <CHECK> 打开 sanitizer(实验性 );配 --shared-libasan
--Werror / --Wno-error 警告升级为错误 / 取消
--warn-on-unstable-apis 对每个用到的不稳定 API 报警告
--ignore-deprecated <NAME> 临时忽略某个弃用告警(迁移期有用)
--experimental-fixit / --experimental-export-fixit <YAML> 输出机器可读的自动修复建议(这是给工具/CI 用的,1.0 迁移就是靠它)
--disable-builtins 不加载 prelude(写 freestanding/裸机代码时用)
--diagnose-missing-doc-strings 缺 docstring 就报警告
--max-notes-per-diagnostic <N> 限制每条诊断附带的 note 数量(复杂报错会刷屏)
-Xlinker <ARG> 透传参数给链接器
--lld-path <PATH> 指定 lld 路径
--diagnostic-format <FORMAT> 诊断输出格式(CI 集成用)
--print-cache-location 打印 .mojo_cache 位置

3.5 mojo repl:交互式探索

bash 复制代码
mojo        # 等价于 mojo repl

REPL 是学习语言和验证 API 的最佳工具。实测会话:

output 复制代码
$ printf 'var x = 40\nprint(x + 2)\ndef f(a: Int) -> Int:\n    return a * 2\nprint(f(21))\nvar v = SIMD[DType.float32, 4](1,2,3,4)\nprint(v.reduce_add())\n' | mojo repl
42
42
10.0
(SIMD[.float32, 4]) v = [1, 2, 3, 4]
(Int) x = 40

几个观察:

  • var 声明会回显类型和值 :(Int) x = 40。这在你不确定类型推断结果时非常有用。
  • 多行 def 直接写就行 ,不需要 Python REPL 那种 ... 续行提示。
  • 标准库自动可用(SIMD、print、List 等无需 import)。

⚠️ 踩坑记录 :在某些精简 Linux 上,mojo repl 会报

output 复制代码
error while loading shared libraries: libncurses.so.6: cannot open shared object file

因为 REPL 依赖 LLDB 的 readline 前端。解决:apt-get install libncurses6(Debian/Ubuntu)或 yum install ncurses-libs。

REPL 里最有价值的用法是探 API:

mojo 复制代码
var s = String("hello world")
print(s.byte_length())
print(s.count_codepoints())
print(s.split(" "))

不确定某个方法存不存在?在 REPL 里试比翻文档快。


3.6 mojo format 与 mblack:代码风格

Mojo 有两个格式化入口:

  1. mojo format ------ 内置的 lint/format 工具:
bash 复制代码
mojo format --help
output 复制代码
DESCRIPTION
        Formats the given set of Mojo sources using a Mojo-specific lint tool.

OPTIONS
    Format options
        --line-length <INTEGER>, -l <INTEGER>
            Sets the max character line length. Default is 80.
  1. mblack ------ 独立可执行文件,"Mojo 版 black",随 mojo 包一起安装:
bash 复制代码
cat ugly.mojo
output 复制代码
def  f( a:Int,b:Int )->Int :
  return a+b
bash 复制代码
mblack --diff ugly.mojo
output 复制代码
--- ugly.mojo
+++ ugly.mojo
@@ -1,2 +1,2 @@
-def  f( a:Int,b:Int )->Int :
-  return a+b
+def f(a: Int, b: Int) -> Int:
+    return a + b
would reformat ugly.mojo

All done! ✨ 🍰 ✨
1 file would be reformatted.
bash 复制代码
mblack ugly.mojo      # 原地格式化
cat ugly.mojo
output 复制代码
def f(a: Int, b: Int) -> Int:
    return a + b

风格约定(与 black 一致):4 空格缩进、80 列(可调)、运算符两侧空格、逗号后空格、双引号字符串。

建议在项目里加 pre-commit hook:

yaml 复制代码
# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: mblack
        name: mblack
        entry: mblack
        language: system
        types_or: [file]
        files: \.mojo$

docstring 也会被检查 :Mojo 的 lint 会警告"docstring 摘要句应以 .、!、? 或反引号结尾"。用中文写 docstring 时,句号 。 会触发这个警告------不影响编译,但 CI 里开 --Werror 时会失败。要么用英文 docstring,要么在 CI 里放行这类警告。


3.7 mojo doc:从 docstring 生成结构化文档

mojo 复制代码
"""A tiny demo module."""

def add(a: Int, b: Int) -> Int:
    """Return the sum of two integers.

    Args:
        a: First addend.
        b: Second addend.

    Returns:
        The sum a + b.
    """
    return a + b
bash 复制代码
mojo doc docdemo.mojo

输出是 JSON(不是 HTML):

output 复制代码
{
  "decl": {
    "aliases": [],
    "description": "",
    "functions": [
      {
        "kind": "function",
        "name": "add",
        "overloads": [
          {
            "args": [
              {
                "convention": "imm",
                "description": "First addend.",
                "kind": "argument",
                "name": "a",
                "passingKind": "pos_or_kw",
                "path": "/std/simd/#int",
                "type": "Int"
              },
              ...

要点:

  • 官方明确说这是"early version",只产出结构化 JSON,不生成 HTML,格式可能变。
  • 输入可以是文件或目录(目录会递归处理所有模块,产出单个 JSON)。
  • -o <PATH> 指定输出文件,不给就写 stdout。
  • JSON 里能看到参数的 convention(这里是 imm,即默认的只读借用约定)------这是理解 Mojo 参数约定的绝佳材料(第 12 章)。

docstring 的规范(Args: / Returns: / Raises: / Parameters: / Constraints: 等段落)见 docstring reference,本书第 26 章会给出完整模板。


3.8 mojo precompile:预编译包

bash 复制代码
mojo precompile src/mylib -o mylib.mojopkg

把一整个包编译成 .mojopkg,下游项目 import 它时不用重新编译源码。这是 Mojo 的二进制分发单位(因为 ABI 不稳定,分发的是"预编译的 Mojo IR"而不是机器码)。

典型用法:

bash 复制代码
# 库作者
mojo precompile src/my_mojo_lib -o $PREFIX/lib/mojo/my_mojo_lib.mojopkg

# 使用者
mojo build -I $PREFIX/lib/mojo app.mojo

第 26 章讲完整的打包发布流程(含 recipe.yaml + rattler-build)。


3.9 mojo debug:调试器

bash 复制代码
mojo debug app.mojo          # 启动 CLI 调试器(默认 -g full)

mojo debug 封装了 LLDB,并加载 Mojo 的数据可视化器(lldb-visualizers),所以 print 一个 List[Int] 会显示成人类可读的形式而不是原始内存。

也支持外部编辑器:

bash 复制代码
mojo debug --editor vscode app.mojo   # 生成 VS Code launch 配置并启动

典型 LLDB 会话:

output 复制代码
(lldb) b main.mojo:12
(lldb) run
(lldb) p x
(lldb) bt
(lldb) n

⚠️ 官方文档坦承:"Please note that there are issues when generating debug info for some Mojo programs that have yet to be addressed. " 遇到调试信息异常时,试试 -g1(只生成行号表)而不是 -g2/full。

第 27 章有完整的调试实战(含条件断点、内存检查、core dump 分析)。


3.10 mojo demangle:还原符号名

Mojo 的符号名经过 mangling(因为泛型要编码参数)。看到一堆乱码符号时:

bash 复制代码
mojo demangle '_ZN4mojo4mainEv'

配合 nm / objdump 使用:

bash 复制代码
nm -C libmylib.so            # -C 自动 demangle
nm libmylib.so | awk '{print $3}' | mojo demangle

第 29 章讲 C FFI 时会大量用到(@export("name") 就是为了绕过 mangling)。


3.11 八个子命令速查

命令 一句话 何时用
mojo run <f> 编译并运行 日常开发
mojo build <f> 编译出可执行文件/库/IR 交付、性能测试、交叉编译
mojo repl 交互式 探 API、验证想法
mojo debug <f> LLDB 调试 排查逻辑错误、崩溃
mojo precompile <pkg> 产出 .mojopkg 库分发、加速大项目编译
mojo format <fs> 格式化 提交前、CI
`mojo doc <f dir>` 产出 docstring JSON
mojo demangle <name> 还原符号 分析二进制、FFI

外加两个独立工具:mblack(格式化)、mojo-lsp-server(编辑器)、gpu-query(GPU 信息,需 MAX)。


3.12 编译缓存与性能

Mojo 有编译缓存(.mojo_cache):

bash 复制代码
mojo --print-cache-location
  • 缓存放在项目目录(或按配置),重复编译相同代码会显著加速;
  • CI 里建议缓存这个目录;
  • 缓存对版本敏感,升级 Mojo 后第一次编译会全量重建。

编译并行度:

bash 复制代码
mojo build -j 8 app.mojo     # 限制到 8 线程(默认用满所有核)

在内存受限的机器上(8 GB),编译大型泛型代码可能 OOM------此时降低 -j 是有效手段。第 26、31 章讨论编译时间的工程化处理。


3.13 一个完整的开发循环(示范)

把本章串起来,一个真实的工作流:

bash 复制代码
# 1. 建项目
mkdir -p demo/src/demo demo/examples && cd demo

# 2. 写库代码
cat > src/demo/__init__.mojo <<'EOF'
from .stats import mean, stddev
EOF

cat > src/demo/stats.mojo <<'EOF'
from std.math import sqrt

def mean(xs: List[Float64]) raises -> Float64:
    """Return the arithmetic mean of a non-empty list."""
    if len(xs) == 0:
        raise Error("mean of empty list")
    var total = 0.0
    for x in xs:
        total += x
    return total / Float64(len(xs))

def stddev(xs: List[Float64]) raises -> Float64:
    """Return the population standard deviation."""
    var m = mean(xs)
    var acc = 0.0
    for x in xs:
        var d = x - m
        acc += d * d
    return sqrt(acc / Float64(len(xs)))
EOF

# 3. 写调用方
cat > examples/run.mojo <<'EOF'
from demo import mean, stddev

def main() raises:
    var xs: List[Float64] = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]
    print("mean   =", mean(xs))
    print("stddev =", stddev(xs))
EOF

# 4. 快速迭代(低优化级别)
mojo run -O0 -I src examples/run.mojo

# 5. 格式化
mblack src/demo/stats.mojo examples/run.mojo

# 6. 发布构建
mojo build -O3 -I src -o run examples/run.mojo
./run

# 7. 看看生成了什么
mojo build -O3 -I src --emit asm examples/run.mojo -o run.s
grep -c vmulpd run.s     # 有没有向量化乘法?

第 4 步输出:

output 复制代码
mean   = 5.0
stddev = 2.0

3.14 本章小结

  • 顶层只能有声明 和 comptime 常量,不能有可执行语句------这是 Mojo 与 Python 的第一个重大差异。
  • mojo run 用于迭代,mojo build 用于交付;选项必须写在文件路径之前。
  • --emit 能产出 asm / llvm / llvm-bitcode / object / shared-lib / air,是理解编译流水线与排查性能的窗口。
  • --fp-mode contract=off 关闭 FMA 收缩,做数值精度对齐时必备。
  • mojo repl 会回显变量类型,是探 API 的利器(需要 libncurses6)。
  • mblack / mojo format 统一风格;mojo doc 产出 JSON(还没有 HTML)。
  • mojo precompile 产 .mojopkg,这是 Mojo 的分发单位(因为 ABI 不稳定)。
  • TString 不支持格式说明符 ,format() 只支持位置索引------对齐/补零要自己写辅助函数。

3.15 动手练习

  1. 把 3.3 的 wc.mojo 扩展成支持 -l(只输出行数)、-w(只输出词数)的选项解析。提示:手工遍历 std.sys.argv() 做前缀匹配即可(Mojo 标准库目前没有 argparse)。

  2. 用 mojo build --emit asm 导出 3.13 的 run.s,搜索 vmulpd / vaddpd,判断 stddev 的循环是否被向量化。再用 -O0 编译一次对比。

  3. 写一个 @export 的函数,mojo build --emit shared-lib 出 .so,然后用 Python ctypes 调用它:

    python 复制代码
    import ctypes
    lib = ctypes.CDLL("./libmylib.so")
    lib.add_one.restype = ctypes.c_int64
    print(lib.add_one(41))
  4. 用 mojo build --print-supported-accelerators 列出你机器上能目标化的加速器架构,记下来(第 32 章要用)。

  5. 故意写三个会编译失败的程序,练习读诊断信息:

    • 缺类型标注的函数参数;
    • 在非 raises 函数里调用 raises 函数;
    • 把 List 直接赋值给另一个变量(不加 .copy() 或 ^)。 把每条错误信息里的 error: / note: 行抄下来,对照附录 D。

下一章 :第 4 章 词法结构、变量与基本类型 ------ 正式开始语言学习。

相关推荐
程序员老刘1 小时前
从短剧App的bug看响应式编程在AI时代的优势
flutter·ai编程·客户端
ServBay2 小时前
API Key 泄露如何在 10 分钟内止血?AI 密钥管理必备
后端·ai编程
ZzT4 小时前
AI 时代吃香的三种人:有想法的懒人、替别人着想的人、不被 AI 带偏的人
人工智能·ai编程
libokaifa4 小时前
别急着让 AI 写代码:需求里的「熵」,才是你真正的成本
ai编程
threerocks5 小时前
妈妈都分不清的配音 | VoiceStudio + VoxCPM 2 本地配音保姆级教程
aigc·openai·ai编程
不老刘5 小时前
Antigravity 的 New Worktree 模式:给 AI 代理一个安全的“平行工位”
人工智能·git·ai编程·antigravity
xn71335 小时前
本地 AI 知识库跨设备同步:SQLite FTS5、Embedding、Tombstone 和引用校验
人工智能·sqlite·ai编程
偷偷摸鱼的少年5 小时前
QQ「大肥鱼」AI 机器人
ai编程