From f7642f933804b036ae3179abb1814accde6b80f4 Mon Sep 17 00:00:00 2001 From: xihale Date: Mon, 30 Jun 2025 17:12:03 +0800 Subject: [PATCH 01/22] Remove obsolete files and configurations, including .gitignore, .gitmodules, and various content files related to Zig documentation and examples. This cleanup enhances project maintainability by eliminating unused resources. --- .github/workflows/examples.yml | 39 - .github/workflows/gh-pages.yml | 64 -- .github/workflows/lint.yml | 31 - .github/workflows/mirror.yml | 18 - .gitignore | 8 - .lintmdrc | 18 - .prettierignore | 3 - Makefile | 20 - README.org | 27 +- archetypes/default.md | 5 - assets/highlight.css | 263 +++++ assets/icons/discord.svg | 6 + assets/icons/email.svg | 4 + assets/icons/github.svg | 19 + assets/icons/{logo.svg => icon.svg} | 16 +- assets/icons/telegram.svg | 4 + assets/icons/zigcc.svg | 31 + {static => assets}/images/blockly.webp | Bin {static => assets}/images/fair-children.webp | Bin .../images/first-online-meeting.webp | Bin {static => assets}/images/logo.svg | 0 {static => assets}/images/paper-cuts.webp | Bin {static => assets}/images/soa-layout.webp | Bin .../stackoverflow2024-highly-desired.webp | Bin .../images/stackoverflow2024-salary.webp | Bin {static => assets}/images/struct-padding.webp | Bin .../images/typed-fsm/2.1-1.webp | Bin .../images/typed-fsm/2.1-2.webp | Bin .../images/typed-fsm/2.1-3.webp | Bin .../images/typed-fsm/2.1-4.webp | Bin .../images/typed-fsm/2.2-1.webp | Bin .../images/typed-fsm/2.3-1.webp | Bin {static => assets}/images/zon-multihash.webp | Bin assets/index.css | 8 + assets/style.css | 184 ++++ config.toml | 119 --- content/_index.md | 31 - content/about.smd | 56 + content/about/_index.org | 46 - .../{community/_index.md => community.smd} | 22 +- content/{contributing.md => contributing.smd} | 19 +- content/index.smd | 20 + content/learn/{preface.md => 01-preface.smd} | 7 +- ...nstalling-zig.md => 02-installing-zig.smd} | 7 +- ...ew-part1.md => 03-language-overview-1.smd} | 25 +- ...ew-part2.md => 04-language-overview-2.smd} | 23 +- .../{style-guide.md => 05-style-guide.smd} | 13 +- .../learn/{pointers.md => 06-pointers.smd} | 37 +- .../{stack-memory.md => 07-stack-memory.smd} | 17 +- ...ry-and-allocator.md => 08-heap-memory.smd} | 25 +- .../learn/{generics.md => 09-generics.smd} | 7 +- ...{coding-in-zig.md => 10-coding-in-zig.smd} | 23 +- .../{conclusion.md => 11-conclusion.smd} | 7 +- content/learn/{_index.md => index.smd} | 30 +- content/monthly/{202207.md => 202207.smd} | 9 +- content/monthly/{202208.md => 202208.smd} | 9 +- content/monthly/{202209.md => 202209.smd} | 13 +- content/monthly/{202210.md => 202210.smd} | 13 +- content/monthly/{202211.md => 202211.smd} | 11 +- content/monthly/202212.md | 13 - content/monthly/202212.smd | 15 + content/monthly/202301.org | 63 -- content/monthly/202301.smd | 67 ++ content/monthly/202302.org | 64 -- content/monthly/202302.smd | 72 ++ content/monthly/202303.org | 93 -- content/monthly/202303.smd | 101 ++ content/monthly/202304.org | 37 - content/monthly/202304.smd | 44 + content/monthly/202305.org | 33 - content/monthly/202305.smd | 40 + content/monthly/202306.org | 85 -- content/monthly/202306.smd | 90 ++ content/monthly/202307.org | 95 -- content/monthly/202307.smd | 102 ++ content/monthly/202308.org | 139 --- content/monthly/202308.smd | 146 +++ content/monthly/202309.org | 55 - content/monthly/202309.smd | 61 ++ content/monthly/202310.org | 79 -- content/monthly/202310.smd | 86 ++ content/monthly/202311.org | 102 -- content/monthly/202311.smd | 109 ++ content/monthly/202402.org | 38 - content/monthly/202402.smd | 45 + content/monthly/{202403.org => 202403.smd} | 120 ++- content/monthly/202404.org | 76 -- content/monthly/202404.smd | 83 ++ content/monthly/202405.org | 63 -- content/monthly/202405.smd | 68 ++ content/monthly/{202406.org => 202406.smd} | 63 +- content/monthly/{202407.org => 202407.smd} | 78 +- content/monthly/202410.org | 68 -- content/monthly/202410.smd | 74 ++ content/monthly/202411.org | 87 -- content/monthly/202411.smd | 93 ++ content/monthly/{_index.md => index.smd} | 9 +- content/post/0.14.md | 192 ---- content/post/2023-09-05-bog-gc-1-en.md | 193 ---- content/post/2023-09-05-bog-gc-1.md | 187 ---- content/post/2023-09-05-hello-world.md | 28 - content/post/2023-09-21-zig-midi.md | 986 ------------------ .../post/2023-10-14-zig-version-manager.org | 92 -- .../2023-12-24-zig-build-explained-part1.md | 335 ------ .../2023-12-28-zig-build-explained-part2.md | 561 ---------- .../2023-12-29-zig-build-explained-part3.md | 502 --------- ...12-how-to-release-your-zig-applications.md | 180 ---- content/post/2024-04-06-zig-cpp.md | 166 --- content/post/2024-05-07-package-hash.md | 237 ----- content/post/2024-05-24-interface-idioms.md | 444 -------- content/post/2024-06-10-zig-hashmap-1.md | 386 ------- content/post/2024-06-11-zig-hashmap-2.md | 372 ------- .../2024-06-16-leveraging-zig-allocator.md | 160 --- content/post/2024-08-12-zoop.md | 484 --------- content/post/2024-11-26-typed-fsm.md | 554 ---------- content/post/2025-01-23-bonkers-comptime.md | 389 ------- content/post/_index.md | 10 - content/post/first-post/fanzine.jpg | Bin 0 -> 124852 bytes content/post/first-post/index.smd | 110 ++ content/post/index.smd | 40 + content/post/news/2023-12-11-first-meetup.org | 40 - .../post/news/2023-12-27-second-meetup.org | 45 - content/post/news/2024-01-14-third-meetup.org | 36 - .../news/2024-04-27-release-party-review.org | 35 - content/post/news/_index.org | 11 - content/post/second-post.smd | 28 + convert_link_format.js | 107 ++ convert_md_to_smd.js | 150 +++ convert_monthly_md_to_smd.js | 149 +++ convert_monthly_org_to_smd.js | 108 ++ .../.tool-versions | 1 - .../Makefile | 5 - .../build.zig | 25 - .../build.zig.zon | 8 - .../src/main.zig | 5 - examples/run-all.sh | 8 - fix_links.js | 134 +++ fix_links_advanced.js | 155 +++ fix_zig_update_section.js | 43 + frpc.ini | 15 + go.mod | 5 - go.sum | 11 - layouts/blog.shtml | 25 + .gitmodules => layouts/community.shtml | 0 layouts/contributing.shtml | 0 layouts/index.shtml | 7 + layouts/learn.shtml | 27 + layouts/monthly.shtml | 25 + layouts/page.shtml | 7 + layouts/partials/hooks/head-end.html | 15 - layouts/post.shtml | 40 + layouts/post.xml | 19 + layouts/templates/base.shtml | 54 + main | 1 + public/about/index.html | 61 ++ public/community/index.html | 61 ++ public/contributing/index.html | 61 ++ public/highlight.css | 37 + public/index.css | 9 + public/index.html | 61 ++ public/learn/coding-in-zig/index.html | 525 ++++++++++ public/learn/conclusion/index.html | 63 ++ public/learn/generics/index.html | 214 ++++ .../heap-memory-and-allocator/index.html | 444 ++++++++ public/learn/index.html | 64 ++ public/learn/installing-zig/index.html | 76 ++ .../learn/language-overview-part1/index.html | 266 +++++ .../learn/language-overview-part2/index.html | 368 +++++++ public/learn/pointers/index.html | 346 ++++++ public/learn/preface/index.html | 63 ++ public/learn/stack-memory/index.html | 154 +++ public/learn/style-guide/index.html | 111 ++ public/monthly/index.html | 0 public/post/first-post/fanzine.jpg | Bin 0 -> 124852 bytes public/post/first-post/index.html | 90 ++ public/post/index.html | 100 ++ public/post/index.xml | 32 + public/post/second-post/index.html | 93 ++ public/style.css | 47 + public/zigcc.svg | 31 + static/favicons/android-chrome-192x192.png | Bin 3627 -> 0 bytes static/favicons/android-chrome-512x512.png | Bin 9606 -> 0 bytes .../android-chrome-maskable-192x192.png | Bin 3627 -> 0 bytes .../android-chrome-maskable-512x512.png | Bin 9606 -> 0 bytes static/favicons/apple-touch-icon.png | Bin 3560 -> 0 bytes static/favicons/favicon.ico | Bin 1150 -> 0 bytes zine.ziggy | 9 + 187 files changed, 6865 insertions(+), 8586 deletions(-) delete mode 100644 .github/workflows/examples.yml delete mode 100644 .github/workflows/gh-pages.yml delete mode 100644 .github/workflows/lint.yml delete mode 100644 .github/workflows/mirror.yml delete mode 100644 .gitignore delete mode 100644 .lintmdrc delete mode 100644 .prettierignore delete mode 100644 Makefile delete mode 100644 archetypes/default.md create mode 100644 assets/highlight.css create mode 100644 assets/icons/discord.svg create mode 100644 assets/icons/email.svg create mode 100644 assets/icons/github.svg rename assets/icons/{logo.svg => icon.svg} (56%) create mode 100644 assets/icons/telegram.svg create mode 100644 assets/icons/zigcc.svg rename {static => assets}/images/blockly.webp (100%) rename {static => assets}/images/fair-children.webp (100%) rename {static => assets}/images/first-online-meeting.webp (100%) rename {static => assets}/images/logo.svg (100%) rename {static => assets}/images/paper-cuts.webp (100%) rename {static => assets}/images/soa-layout.webp (100%) rename {static => assets}/images/stackoverflow2024-highly-desired.webp (100%) rename {static => assets}/images/stackoverflow2024-salary.webp (100%) rename {static => assets}/images/struct-padding.webp (100%) rename {static => assets}/images/typed-fsm/2.1-1.webp (100%) rename {static => assets}/images/typed-fsm/2.1-2.webp (100%) rename {static => assets}/images/typed-fsm/2.1-3.webp (100%) rename {static => assets}/images/typed-fsm/2.1-4.webp (100%) rename {static => assets}/images/typed-fsm/2.2-1.webp (100%) rename {static => assets}/images/typed-fsm/2.3-1.webp (100%) rename {static => assets}/images/zon-multihash.webp (100%) create mode 100644 assets/index.css create mode 100644 assets/style.css delete mode 100644 config.toml delete mode 100644 content/_index.md create mode 100644 content/about.smd delete mode 100644 content/about/_index.org rename content/{community/_index.md => community.smd} (55%) rename content/{contributing.md => contributing.smd} (74%) create mode 100644 content/index.smd rename content/learn/{preface.md => 01-preface.smd} (79%) rename content/learn/{installing-zig.md => 02-installing-zig.smd} (90%) rename content/learn/{language-overview-part1.md => 03-language-overview-1.smd} (96%) rename content/learn/{language-overview-part2.md => 04-language-overview-2.smd} (97%) rename content/learn/{style-guide.md => 05-style-guide.smd} (92%) rename content/learn/{pointers.md => 06-pointers.smd} (95%) rename content/learn/{stack-memory.md => 07-stack-memory.smd} (97%) rename content/learn/{heap-memory-and-allocator.md => 08-heap-memory.smd} (98%) rename content/learn/{generics.md => 09-generics.smd} (98%) rename content/learn/{coding-in-zig.md => 10-coding-in-zig.smd} (97%) rename content/learn/{conclusion.md => 11-conclusion.smd} (93%) rename content/learn/{_index.md => index.smd} (77%) rename content/monthly/{202207.md => 202207.smd} (90%) rename content/monthly/{202208.md => 202208.smd} (95%) rename content/monthly/{202209.md => 202209.smd} (94%) rename content/monthly/{202210.md => 202210.smd} (86%) rename content/monthly/{202211.md => 202211.smd} (93%) delete mode 100644 content/monthly/202212.md create mode 100644 content/monthly/202212.smd delete mode 100644 content/monthly/202301.org create mode 100644 content/monthly/202301.smd delete mode 100644 content/monthly/202302.org create mode 100644 content/monthly/202302.smd delete mode 100644 content/monthly/202303.org create mode 100644 content/monthly/202303.smd delete mode 100644 content/monthly/202304.org create mode 100644 content/monthly/202304.smd delete mode 100644 content/monthly/202305.org create mode 100644 content/monthly/202305.smd delete mode 100644 content/monthly/202306.org create mode 100644 content/monthly/202306.smd delete mode 100644 content/monthly/202307.org create mode 100644 content/monthly/202307.smd delete mode 100644 content/monthly/202308.org create mode 100644 content/monthly/202308.smd delete mode 100644 content/monthly/202309.org create mode 100644 content/monthly/202309.smd delete mode 100644 content/monthly/202310.org create mode 100644 content/monthly/202310.smd delete mode 100644 content/monthly/202311.org create mode 100644 content/monthly/202311.smd delete mode 100644 content/monthly/202402.org create mode 100644 content/monthly/202402.smd rename content/monthly/{202403.org => 202403.smd} (57%) delete mode 100644 content/monthly/202404.org create mode 100644 content/monthly/202404.smd delete mode 100644 content/monthly/202405.org create mode 100644 content/monthly/202405.smd rename content/monthly/{202406.org => 202406.smd} (65%) rename content/monthly/{202407.org => 202407.smd} (62%) delete mode 100644 content/monthly/202410.org create mode 100644 content/monthly/202410.smd delete mode 100644 content/monthly/202411.org create mode 100644 content/monthly/202411.smd rename content/monthly/{_index.md => index.smd} (77%) delete mode 100644 content/post/0.14.md delete mode 100644 content/post/2023-09-05-bog-gc-1-en.md delete mode 100644 content/post/2023-09-05-bog-gc-1.md delete mode 100644 content/post/2023-09-05-hello-world.md delete mode 100644 content/post/2023-09-21-zig-midi.md delete mode 100644 content/post/2023-10-14-zig-version-manager.org delete mode 100644 content/post/2023-12-24-zig-build-explained-part1.md delete mode 100644 content/post/2023-12-28-zig-build-explained-part2.md delete mode 100644 content/post/2023-12-29-zig-build-explained-part3.md delete mode 100644 content/post/2024-01-12-how-to-release-your-zig-applications.md delete mode 100644 content/post/2024-04-06-zig-cpp.md delete mode 100644 content/post/2024-05-07-package-hash.md delete mode 100644 content/post/2024-05-24-interface-idioms.md delete mode 100644 content/post/2024-06-10-zig-hashmap-1.md delete mode 100644 content/post/2024-06-11-zig-hashmap-2.md delete mode 100644 content/post/2024-06-16-leveraging-zig-allocator.md delete mode 100644 content/post/2024-08-12-zoop.md delete mode 100644 content/post/2024-11-26-typed-fsm.md delete mode 100644 content/post/2025-01-23-bonkers-comptime.md delete mode 100644 content/post/_index.md create mode 100644 content/post/first-post/fanzine.jpg create mode 100644 content/post/first-post/index.smd create mode 100644 content/post/index.smd delete mode 100644 content/post/news/2023-12-11-first-meetup.org delete mode 100644 content/post/news/2023-12-27-second-meetup.org delete mode 100644 content/post/news/2024-01-14-third-meetup.org delete mode 100644 content/post/news/2024-04-27-release-party-review.org delete mode 100644 content/post/news/_index.org create mode 100644 content/post/second-post.smd create mode 100644 convert_link_format.js create mode 100644 convert_md_to_smd.js create mode 100644 convert_monthly_md_to_smd.js create mode 100644 convert_monthly_org_to_smd.js delete mode 100644 examples/2024-01-12-how-to-release-your-zig-applications/.tool-versions delete mode 100644 examples/2024-01-12-how-to-release-your-zig-applications/Makefile delete mode 100644 examples/2024-01-12-how-to-release-your-zig-applications/build.zig delete mode 100644 examples/2024-01-12-how-to-release-your-zig-applications/build.zig.zon delete mode 100644 examples/2024-01-12-how-to-release-your-zig-applications/src/main.zig delete mode 100755 examples/run-all.sh create mode 100644 fix_links.js create mode 100644 fix_links_advanced.js create mode 100644 fix_zig_update_section.js create mode 100644 frpc.ini delete mode 100644 go.mod delete mode 100644 go.sum create mode 100644 layouts/blog.shtml rename .gitmodules => layouts/community.shtml (100%) create mode 100644 layouts/contributing.shtml create mode 100644 layouts/index.shtml create mode 100644 layouts/learn.shtml create mode 100644 layouts/monthly.shtml create mode 100644 layouts/page.shtml delete mode 100644 layouts/partials/hooks/head-end.html create mode 100644 layouts/post.shtml create mode 100644 layouts/post.xml create mode 100644 layouts/templates/base.shtml create mode 160000 main create mode 100644 public/about/index.html create mode 100644 public/community/index.html create mode 100644 public/contributing/index.html create mode 100644 public/highlight.css create mode 100644 public/index.css create mode 100644 public/index.html create mode 100644 public/learn/coding-in-zig/index.html create mode 100644 public/learn/conclusion/index.html create mode 100644 public/learn/generics/index.html create mode 100644 public/learn/heap-memory-and-allocator/index.html create mode 100644 public/learn/index.html create mode 100644 public/learn/installing-zig/index.html create mode 100644 public/learn/language-overview-part1/index.html create mode 100644 public/learn/language-overview-part2/index.html create mode 100644 public/learn/pointers/index.html create mode 100644 public/learn/preface/index.html create mode 100644 public/learn/stack-memory/index.html create mode 100644 public/learn/style-guide/index.html create mode 100644 public/monthly/index.html create mode 100644 public/post/first-post/fanzine.jpg create mode 100644 public/post/first-post/index.html create mode 100644 public/post/index.html create mode 100644 public/post/index.xml create mode 100644 public/post/second-post/index.html create mode 100644 public/style.css create mode 100644 public/zigcc.svg delete mode 100644 static/favicons/android-chrome-192x192.png delete mode 100644 static/favicons/android-chrome-512x512.png delete mode 100644 static/favicons/android-chrome-maskable-192x192.png delete mode 100644 static/favicons/android-chrome-maskable-512x512.png delete mode 100644 static/favicons/apple-touch-icon.png delete mode 100644 static/favicons/favicon.ico create mode 100644 zine.ziggy diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml deleted file mode 100644 index 1cce14f..0000000 --- a/.github/workflows/examples.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Run Examples - -on: - schedule: - - cron: "10 20 * * *" - pull_request: - paths: - - "examples/**" - - ".github/workflows/examples.yml" - push: - branches: - - main - paths: - - "examples/**" - - ".github/workflows/examples.yml" - -defaults: - run: - working-directory: examples - -jobs: - examples: - timeout-minutes: 10 - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest] - zig: [0.12.0] - steps: - - uses: actions/checkout@v4 - - - uses: goto-bus-stop/setup-zig@v2 - with: - version: ${{ matrix.zig }} - - - name: Run Examples - run: | - ./run-all.sh diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml deleted file mode 100644 index 3d09ca6..0000000 --- a/.github/workflows/gh-pages.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: GitHub Pages - -on: - push: - branches: - - main - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: true - -# Default to bash -defaults: - run: - shell: bash - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - uses: peaceiris/actions-hugo@v2 - with: - # hugo-version: "0.140.2" - hugo-version: latest - extended: true - - name: Setup Pages - id: pages - uses: actions/configure-pages@v4 - - name: Build with Hugo - env: - # For maximum backward compatibility with Hugo modules - HUGO_ENVIRONMENT: production - HUGO_ENV: production - run: | - npm i -D postcss postcss-cli autoprefixer - hugo mod get - hugo \ - --minify \ - --baseURL "${{ steps.pages.outputs.base_url }}/" - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ./public - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 339e2e0..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Lint - -on: - workflow_dispatch: - pull_request: - paths: - - "**.md" - - "**.org" - - ".github/workflows/**" - push: - branches: - - main - paths: - - "**.md" - - "**.org" - - ".github/workflows/**" - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-node@v4 - with: - node-version: "latest" - - name: Prettier check - run: | - echo "If lint fails, run `make format` and commit again." - make lint diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml deleted file mode 100644 index 71ae1b4..0000000 --- a/.github/workflows/mirror.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Mirror - -on: - push: - branches: - - main - workflow_dispatch: - -jobs: - codeberg: - if: github.repository_owner == 'zigcc' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - run: | - git push --tags --force https://${{ secrets.CBTOKEN }}@codeberg.org/jiacai2050/zigcc.git "refs/remotes/origin/*:refs/heads/*" diff --git a/.gitignore b/.gitignore deleted file mode 100644 index e2e84ff..0000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -*.lock -.DS_Store -/public -/resources - -# zig -zig-cache/ -zig-out/ diff --git a/.lintmdrc b/.lintmdrc deleted file mode 100644 index 3f61869..0000000 --- a/.lintmdrc +++ /dev/null @@ -1,18 +0,0 @@ -{ - "rules":{ - "no-empty-code": 2, - "no-empty-url": 2, - "no-empty-list": 2, - "space-around-alphabet": 2, - "no-special-characters": 2, - "no-multiple-space-blockquote": 2, - "correct-title-trailing-punctuation": 2, - "no-empty-blockquote": 2, - "use-standard-ellipsis": 2, - "space-around-number": 2, - "no-long-code": [2, { - "length": 100, - "exclude": ["dot"] - }] - } -} \ No newline at end of file diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 02dfa4c..0000000 --- a/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -/public -/themes -/layouts \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 7fe6b24..0000000 --- a/Makefile +++ /dev/null @@ -1,20 +0,0 @@ - -serve: - hugo serve - -# 初始化下载,更新到 .gitmodules 中指定的 commit -init: - git submodule update --init - -lint: - npx @lint-md/cli **/* - -format: - npx @lint-md/cli --fix **/* - - -EXCLUDE = --exclude "*webp" --exclude "*svg" --exclude "*gif" -IMG_PATH = ./static/images -webp: - fd -t f $(EXCLUDE) --full-path $(IMG_PATH) --exec convert {} {.}.webp \; - fd -t f $(EXCLUDE) --full-path $(IMG_PATH) --exec rm {} \; diff --git a/README.org b/README.org index 199641c..0417ffd 100644 --- a/README.org +++ b/README.org @@ -24,29 +24,4 @@ Zig Chinese Community is dedicated to sharing and spreading the use of Zig language among Chinese users. #+end_quote -本网站使用 [[https://gohugo.io/][hugo]](extended 版本)与 [[https://www.docsy.dev/][docsy]] 主题进行构建。 - -#+begin_src bash -# For macOS -brew install hugo -# For Debian -sudo apt install hugo -# For Arch -sudo pacman -S hugo - -# Snap -sudo snap install hugo -#+end_src - -#+begin_src bash -# run server -hugo server -#+end_src - -如果启动 server 时报错,可以安装以下依赖: -#+begin_src bash -# install depends -npm i -D postcss postcss-cli autoprefixer -#+end_src - -参考:https://www.docsy.dev/docs/get-started/docsy-as-module/installation-prerequisites/#install-postcss +TODO: Contribute docs \ No newline at end of file diff --git a/archetypes/default.md b/archetypes/default.md deleted file mode 100644 index 26f317f..0000000 --- a/archetypes/default.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ .Date }} -draft: true ---- diff --git a/assets/highlight.css b/assets/highlight.css new file mode 100644 index 0000000..754422e --- /dev/null +++ b/assets/highlight.css @@ -0,0 +1,263 @@ +code .comment { + color: var(--comment-gray); +} + +code.html { +} + +code.html .string { + color: var(--dark-yellow); +} + +code.html .tag { + color: var(--blue); +} + +code.html .constant { + color: var(--light-red); +} + +code.html .attribute { + color: var(--light-yellow); +} + +code.html .punctuation { + color: var(--cyan); +} + +code.superhtml { +} + +code.superhtml .string.markup_link_url { + text-decoration: underline; + color: var(--blue); +} + +code.superhtml .string { + color: var(--dark-yellow); +} + +code.superhtml .tag { + color: var(--blue); +} + +code.superhtml .special { + color: var(--blue); +} + +code.superhtml .constant { + color: var(--light-red); +} + +code.superhtml .attribute { + color: var(--light-yellow); +} + +code.superhtml .punctuation { + color: var(--cyan); +} + +code.zig { +} + +code.zig .string { + color: var(--dark-yellow); +} + +code.zig .variable, +code.zig .field { + color: var(--light-yellow); +} + +code.zig .keyword.function { + color: var(--light-red); +} + +code.zig .bracket { + color: var(--cyan); +} + +code.zig .function { + color: var(--blue); +} + +code.zig .builtin { + color: var(--magenta); +} + +code.zig .operator, +code.zig .qualifier, +code.zig .attribute { + color: var(--light-red); +} + +code.ziggy { + color: var(--cyan); +} + +code.ziggy .keyword, +code.ziggy .type { + color: var(--light-yellow); +} + +code.ziggy .string { + color: var(--dark-yellow); +} + +code.ziggy .numeric.constant { + color: var(--magenta); +} + +code.ziggy .function { + color: var(--blue); +} + +code.ziggy-schema { + color: var(--cyan); +} + +code.ziggy-schema .keyword, +code.ziggy-schema .type { + color: var(--light-yellow); +} + +code.ziggy-schema .string { + color: var(--dark-yellow); +} + +code.ziggy-schema .numeric.constant { + color: var(--magenta); +} + +code.ziggy-schema .function { + color: var(--blue); +} + +code.markdown { + color: var(--code-fg); +} + +code.markdown .text.title { + color: var(--light-red); +} + +code.markdown .punctuation { + color: var(--light-red); +} + +code.toml { + color: var(--cyan); +} + +code.toml .property { + color: var(--light-yellow); +} + +code.toml .string { + color: var(--dark-yellow); +} + +code.toml .numeric.constant { + color: var(--magenta); +} + +code.toml .punctuation { + color: var(--blue); +} + +code.lua { +} + +code.lua .string { + color: var(--dark-yellow); +} + +code.lua .variable, +code.lua .field { + color: var(--light-yellow); +} + +code.lua .keyword.function { + color: var(--light-red); +} + +code.lua .bracket { + color: var(--cyan); +} + +code.lua .function { + color: var(--blue); +} + +code.lua .builtin { + color: var(--magenta); +} + +code.lua .operator, +code.lua .qualifier, +code.lua .attribute { + color: var(--light-red); +} + +code.bash { + display: block; /* Ensures it takes up full width for padding/margin */ + padding: 15px; + border-radius: 8px; + overflow-x: auto; /* Enable horizontal scrolling for long lines */ +} + +code.bash .function { + color: var(--blue); /* Using --blue for commands and functions */ +} + +code.bash .string { + color: var(--dark-yellow); /* Using --dark-yellow for strings */ +} + +code.bash .comment { + color: var(--comment-gray); /* Using --comment-gray for comments */ + font-style: italic; +} + +code.bash .operator, +code.bash .property { + color: var(--light-red); /* Using --light-red for operators and properties */ +} + +:root { + --light-yellow: #b58900; + --dark-yellow: #b57614; + --blue: #268bd2; + --cyan: #2aa198; + --light-red: #dc322f; + --dark-red: #cb4b16; + --comment-gray: #657b83; + --magenta: #6c71c4; +} +code.markdown { + color: #333; +} + +code, +pre { + background: var(--code-bg) !important; + color: var(--code-fg) !important; +} + +code.conf { + color: var(--cyan); +} + +code.conf .punctuation_bracket { + color: var(--blue); /* [section] */ + font-weight: bold; +} + +code.conf .function { + color: var(--light-yellow); /* key */ +} + +code.conf .comment { + color: var(--comment-gray); /* ; comment or # comment */ + font-style: italic; +} diff --git a/assets/icons/discord.svg b/assets/icons/discord.svg new file mode 100644 index 0000000..afade27 --- /dev/null +++ b/assets/icons/discord.svg @@ -0,0 +1,6 @@ + + + +discord + + \ No newline at end of file diff --git a/assets/icons/email.svg b/assets/icons/email.svg new file mode 100644 index 0000000..04c39cb --- /dev/null +++ b/assets/icons/email.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/icons/github.svg b/assets/icons/github.svg new file mode 100644 index 0000000..2dfec51 --- /dev/null +++ b/assets/icons/github.svg @@ -0,0 +1,19 @@ + + + + + github [#142] + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/logo.svg b/assets/icons/icon.svg similarity index 56% rename from assets/icons/logo.svg rename to assets/icons/icon.svg index b2d5500..640c300 100644 --- a/assets/icons/logo.svg +++ b/assets/icons/icon.svg @@ -1,16 +1,16 @@ - - - + + + - + - - - + + + - + \ No newline at end of file diff --git a/assets/icons/telegram.svg b/assets/icons/telegram.svg new file mode 100644 index 0000000..541b9d2 --- /dev/null +++ b/assets/icons/telegram.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/icons/zigcc.svg b/assets/icons/zigcc.svg new file mode 100644 index 0000000..85801dc --- /dev/null +++ b/assets/icons/zigcc.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/images/blockly.webp b/assets/images/blockly.webp similarity index 100% rename from static/images/blockly.webp rename to assets/images/blockly.webp diff --git a/static/images/fair-children.webp b/assets/images/fair-children.webp similarity index 100% rename from static/images/fair-children.webp rename to assets/images/fair-children.webp diff --git a/static/images/first-online-meeting.webp b/assets/images/first-online-meeting.webp similarity index 100% rename from static/images/first-online-meeting.webp rename to assets/images/first-online-meeting.webp diff --git a/static/images/logo.svg b/assets/images/logo.svg similarity index 100% rename from static/images/logo.svg rename to assets/images/logo.svg diff --git a/static/images/paper-cuts.webp b/assets/images/paper-cuts.webp similarity index 100% rename from static/images/paper-cuts.webp rename to assets/images/paper-cuts.webp diff --git a/static/images/soa-layout.webp b/assets/images/soa-layout.webp similarity index 100% rename from static/images/soa-layout.webp rename to assets/images/soa-layout.webp diff --git a/static/images/stackoverflow2024-highly-desired.webp b/assets/images/stackoverflow2024-highly-desired.webp similarity index 100% rename from static/images/stackoverflow2024-highly-desired.webp rename to assets/images/stackoverflow2024-highly-desired.webp diff --git a/static/images/stackoverflow2024-salary.webp b/assets/images/stackoverflow2024-salary.webp similarity index 100% rename from static/images/stackoverflow2024-salary.webp rename to assets/images/stackoverflow2024-salary.webp diff --git a/static/images/struct-padding.webp b/assets/images/struct-padding.webp similarity index 100% rename from static/images/struct-padding.webp rename to assets/images/struct-padding.webp diff --git a/static/images/typed-fsm/2.1-1.webp b/assets/images/typed-fsm/2.1-1.webp similarity index 100% rename from static/images/typed-fsm/2.1-1.webp rename to assets/images/typed-fsm/2.1-1.webp diff --git a/static/images/typed-fsm/2.1-2.webp b/assets/images/typed-fsm/2.1-2.webp similarity index 100% rename from static/images/typed-fsm/2.1-2.webp rename to assets/images/typed-fsm/2.1-2.webp diff --git a/static/images/typed-fsm/2.1-3.webp b/assets/images/typed-fsm/2.1-3.webp similarity index 100% rename from static/images/typed-fsm/2.1-3.webp rename to assets/images/typed-fsm/2.1-3.webp diff --git a/static/images/typed-fsm/2.1-4.webp b/assets/images/typed-fsm/2.1-4.webp similarity index 100% rename from static/images/typed-fsm/2.1-4.webp rename to assets/images/typed-fsm/2.1-4.webp diff --git a/static/images/typed-fsm/2.2-1.webp b/assets/images/typed-fsm/2.2-1.webp similarity index 100% rename from static/images/typed-fsm/2.2-1.webp rename to assets/images/typed-fsm/2.2-1.webp diff --git a/static/images/typed-fsm/2.3-1.webp b/assets/images/typed-fsm/2.3-1.webp similarity index 100% rename from static/images/typed-fsm/2.3-1.webp rename to assets/images/typed-fsm/2.3-1.webp diff --git a/static/images/zon-multihash.webp b/assets/images/zon-multihash.webp similarity index 100% rename from static/images/zon-multihash.webp rename to assets/images/zon-multihash.webp diff --git a/assets/index.css b/assets/index.css new file mode 100644 index 0000000..e1c28f5 --- /dev/null +++ b/assets/index.css @@ -0,0 +1,8 @@ +.content{ + font-size: 1.2rem; + display: flex; + flex-direction: column; +} +#logo{ + width: 40vw; +} \ No newline at end of file diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..c58daec --- /dev/null +++ b/assets/style.css @@ -0,0 +1,184 @@ +:root { + --bg: #fff; + --fg: #111; + --border: #aaa; + --link: #0070f3; +} +@media (prefers-color-scheme: dark) { + :root { + --bg: #181818; + --fg: #eee; + --border: #444; + --link: #4ea1ff; + } +} + +.center{ + display: flex; + justify-content: center; + align-items: center; +} +.large{ + font-size: 2rem; + line-height: 2; +} +.bold{ + font-weight: bold; +} + +body { + margin: auto; + max-width: 60vw; + line-height: 1.5; + background: var(--bg); + color: var(--fg); +} + +a { + color: inherit; +} + +p:has(+ pre) { + margin-bottom: 0; +} + +p + pre { + margin-top: 5px; +} + +pre { + overflow: auto; + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); + padding: 4px 0; +} + +pre code { + tab-size: 4; + -moz-tab-size: 4; +} + +code { + font-variant-ligatures: none; + font-feature-settings: "liga" 0; + font-family: "JetBrains Mono", monospace; +} + +p > code, +a > code, +h1 > code, +h2 > code, +h3 > code, +h4 > code, +h5 > code, +li > code { + font-weight: bold; + font-size: 1em; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; +} +.site-title { + display: inline-block; + text-decoration: none; + color: inherit; +} + +nav { + display: inline-block; +} +nav a { + text-decoration: none; + font-size: large; + margin: 0 10px; +} + +footer > div { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +footer > div div { + margin: auto 0; +} + +footer > div i { + font-size: 24px; + margin: 0 5px; +} + +footer > div a { + text-decoration: none; +} + +footer > div img { + height: 1.5em; + width: auto; + vertical-align: middle; + + filter: invert(0%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(0%) contrast(100%); +} + +@media (prefers-color-scheme: dark) { + footer > div img { + filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(1.2) contrast(100%); + } +} + +#prev-next { + display: flex; + justify-content: space-between; + align-items: center; +} + +#prev-next a { + text-decoration: none; +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; + background: var(--bg); +} +::-webkit-scrollbar-thumb { + background: #ccc; + border-radius: 8px; + border: 2px solid var(--bg); + min-height: 40px; +} +::-webkit-scrollbar-thumb:hover { + background: #bbb; +} +::-webkit-scrollbar-corner { + background: var(--bg); +} + +@media (prefers-color-scheme: dark) { + ::-webkit-scrollbar { + background: var(--bg); + } + ::-webkit-scrollbar-thumb { + background: #444; + border: 2px solid var(--bg); + } + ::-webkit-scrollbar-thumb:hover { + background: #555; + } + ::-webkit-scrollbar-corner { + background: var(--bg); + } +} + +html { + scrollbar-width: thin; + scrollbar-color: #ccc var(--bg); +} +@media (prefers-color-scheme: dark) { + html { + scrollbar-color: #444 var(--bg); + } +} \ No newline at end of file diff --git a/config.toml b/config.toml deleted file mode 100644 index efa402f..0000000 --- a/config.toml +++ /dev/null @@ -1,119 +0,0 @@ -baseURL = 'https://ziglang.cc' -languageCode = 'en' -defaultContentLanguage = 'zh-cn' -title = 'Zig 语言中文社区' -description = 'Zig Chinese Community is dedicated to sharing and spreading the use of Zig language among Chinese users.' -copyright = """ © 2022–2025 - | Source Code - | BY-NC-ND 4.0 - | RSS -""" - -[[menu.main]] - name = "学习 Zig" - weight = 11 - url = "/learn" -[[menu.main]] - name = "月刊" - weight = 20 - url = "/monthly" -[[menu.main]] - name = "博客" - weight = 21 - url = "/post" -[[menu.main]] - name = "贡献" - weight = 30 - url = "/contributing" -[[menu.main]] - name = "社区" - weight = 40 - url = "/community" - -[params] - github_repo = "https://github.com/zigcc/zigcc.github.io" - offlineSearch = true - time_format_blog = "2006-01-02" - [params.giscus] - repo = "zigcc/zigcc.github.io" - repoId = "R_kgDOHr7nSg" - category = "Announcements" - categoryId = "DIC_kwDOHr7nSs4CQUsK" - -[params.ui] -# navbar_logo = false -showLightDarkModeMenu = true - -[params.links] -[[params.links.user]] - name = "GitHub Discussion" - url = "https://github.com/orgs/zigcc/discussions" - icon = "fab fa-github" - desc = "新手提问;经验分享" -[[params.links.user]] - name = "Discord" - url = "https://discord.gg/UraRxD6WXD" - icon = "fab fa-discord" -[[params.links.user]] - name = "Telegram" - url = "https://t.me/ZigChinese" - icon = "fab fa-telegram" -[[params.links.user]] - name = "微信群" - url = "https://github.com/orgs/zigcc/discussions/134" - icon = "fab fa-weixin" -[[params.links.user]] - name = "微信公众号" - url = "https://github.com/zigcc/.github/blob/main/zig_mp.png" - icon = "fab fa-weixin" - desc = "跟进社区最新动态" -[[params.links.user]] - name = "邮件" - url = "mailto:hello@ziglang.cc" - icon = "fa fa-envelope" - desc = "" - -[[params.links.developer]] - name = "文章供稿" - url = "/post/2023/09/05/hello-world/" - icon = "fab fa-blogger" - desc = "分享 Zig 使用经验,改进网站体验" -[[params.links.developer]] - name = "Zig 圣经" - url = "https://course.ziglang.cc/" - icon = "fa fa-book" - desc = "完善、改进圣经内容" -[[params.links.developer]] - name = "线上会议" - url = "/post/news" - icon = "fa-brands fa-meetup" - desc = "我们会定期举行线上会议,畅聊 Zig 中的一切" - -[module] -# proxy = "https://goproxy.cn,direct" - [[module.imports]] - path = "github.com/google/docsy" - -[frontmatter] - date = [":filename", ":default", ":fileModTime"] - lastmod = ['lastmod', ':git', ':fileModTime', ':default'] - -[permalinks] - post = "/post/:year/:month/:day/:slug/" - -[markup] - [markup.tableOfContents] - startLevel = 1 - endLevel = 8 - [markup.goldmark] - [markup.goldmark.renderer] - unsafe = true - [markup.goldmark.parser.attribute] - block = true - # https://gohugo.io/getting-started/configuration-markup/#highlight - [markup.highlight] - style = "pygments" - linenos = true - -[outputs] -section = [ "HTML", "RSS", "print" ] diff --git a/content/_index.md b/content/_index.md deleted file mode 100644 index a0e4838..0000000 --- a/content/_index.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Zig 语言中文社区 ---- - -{{% blocks/cover height="full" %}} - - - -Zig 中文社区致力于在中文用户中分享和传播 Zig 语言! - -我们是一群对 Zig 编程语言充满热情的开发者、学习者和爱好者。我们致力于: - -1. 分享 Zig 相关的知识和经验 -2. 翻译 Zig 官方文档和重要资源 -3. 组织线上线下的学习活动和讨论 -4. 推广 Zig 语言在中文开发者中的应用 - -无论你是 Zig 专家还是初学者,[我们都欢迎你的加入](/community)! - -贡献 - -学习 - - - - -{{% /blocks/cover %}} diff --git a/content/about.smd b/content/about.smd new file mode 100644 index 0000000..3642810 --- /dev/null +++ b/content/about.smd @@ -0,0 +1,56 @@ +--- +.title = "About", +.date = @date("1990-01-01T00:00:00"), +.author = "ZigCC", +.layout = "index.shtml", +.draft = false, +--- + +## About Zine +Zine is an MIT-licensed project created by [Loris Cro](https://kristoff.it) and +other contributors listed on the [official repository](https://github.com/kristoff-it/zine/contributors). + +Zine is inspired by [Hugo](https://gohugo.io) but features an entirely custom set +of authoring languages: + +- [Scripty](https://zine-ssg.io/docs/scripty/) is the small expression + language that both SuperHTML and SuperMD share to express templating logic. + +- [SuperHTML](https://zine-ssg.io/docs/superhtml/) is the HTML templating + language used by Zine. Unlike most `{{curly braced}}` templating languages, + SuperHTML uses valid HTML syntax to express the templating logic, adding only + minor extensions to normal HTML. + Thanks to this approach, it offers instant + syntax checking and autoformatting via [a CLI tool](https://github.com/kristoff-it/superhtml) as well as Language Server support ([VScode Extension](https://marketplace.visualstudio.com/items?itemName=LorisCro.super)). + + ># [NOTE]($block) + >The correct file extension for SuperHTML templates is `.shtml`. + +- [SuperMD](https://zine-ssg.io/docs/supermd/) is a superset of Markdown + that, instead of relying on inline HTML, offers new constructs for expressing + content embeds without pulling into your content needless layouting concerns. + A CLI tool and language server for SuperMD is in the works. + + ># [NOTE]($block) + >The correct file extension for SuperMD pages is `.smd`. + +## Zine is alpha software + +Zine is not yet complete. The main functionality is present and you will be able +to build even moderately complex static websites without issue. + +That said using Zine today does imply participating in the development process +to some degree, which usually means inquiring about the development status of +a feature you need, or reporting a bug. + + +Here are some quicklinks related to Zine: + +- Official Website: https://zine-ssg.io/ +- Source Code: https://github.com/kristoff-it/zine/ +- Discord: https://discord.com/invite/B73sGxF + + + + + diff --git a/content/about/_index.org b/content/about/_index.org deleted file mode 100644 index 59a03d3..0000000 --- a/content/about/_index.org +++ /dev/null @@ -1,46 +0,0 @@ -#+TITLE: 学习 Zig -#+DATE: 2024-08-04T08:51:07+0800 -#+LASTMOD: 2024-08-18T11:48:26+0800 -{{% blocks/cover title="学习 Zig" image_anchor="bottom" height="auto" %}} - - -{{% /blocks/cover %}} - -{{% blocks/section %}} - -* 资料 -由于 Zig 目前还处于快速迭代,因此最权威的资料无疑是官方的 [[https://ziglang.org/documentation/master/][Zig Language Reference]],遇到语言的细节问题,基本都可以在这里找到答案。 -其次是社区的一些高质量教程,例如: -- [[https://zig.guide/][Zig Guide]] :: 英文资料, [[https://github.com/Sobeston][Sobeston]] 用户编写 -- [[https://gist.github.com/ityonemo/769532c2017ed9143f3571e5ac104e50][Zig in 30 minutes]] :: -- [[https://ziglang.cc/learning-zig/][学习 Zig]] :: 该系列教程最初由 Karl Seguin 编写,该教程行文流畅,讲述的脉络由浅入深,深入浅出,是入门 Zig 非常不错的选择 -- [[https://course.ziglang.cc][Zig 语言圣经]] :: 一份内容全面、深入浅出介绍 Zig 的教程 -- [[https://codeberg.org/ziglings/exercises/][ziglings/exercises]] :: Learn the Zig programming language by fixing tiny broken programs. -- [[https://cookbook.ziglang.cc/][Zig Cookbook]] :: A collection of simple Zig programs that demonstrate good practices to accomplish common programming tasks -- [[https://github.com/zigcc/awesome-zig][Awesome Zig]] :: A collection of some awesome public Zig programming language projects. - -* 版本管理 -推荐使用版本管理工具 [[/post/2023/10/14/zig-version-manager/][asdf]] 来安装 Zig,具体步骤: -#+begin_src bash -git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0 -cat <<'EOF' >> $HOME/.bashrc -source "$HOME/.asdf/asdf.sh" -source "$HOME/.asdf/completions/asdf.bash" -EOF - -asdf plugin-add zig https://github.com/zigcc/asdf-zig.git - -# 安装最新版 -asdf install zig latest -asdf global zig latest -zig version -#+end_src - -{{% /blocks/section %}} - -{{% blocks/section %}} - -# This is another section -{.text-center} - -{{% /blocks/section %}} diff --git a/content/community/_index.md b/content/community.smd similarity index 55% rename from content/community/_index.md rename to content/community.smd index dba0271..debd6a2 100644 --- a/content/community/_index.md +++ b/content/community.smd @@ -1,22 +1,18 @@ --- -title: 社区 -params: - contributingUrl: /contributing ---- - -{{% blocks/section %}} -让我们一起探索 Zig 的魅力,推动 Zig 在中文社区内的发展! -{.text-center} - -{{% /blocks/section %}} - -{{% blocks/section color="white" %}} +.title = "About", +.date = @date("1990-01-01T00:00:00"), +.author = "ZigCC", +.layout = "index.shtml", +.draft = false, +--- +TODO: issue +[让我们一起探索 Zig 的魅力,推动 Zig 在中文社区内的发展!]($text.attrs('center','large','bold')) # 网站更新日志 +- **2025-06-30:** 切换到 [zine](https://zine-ssg.io/) - **2024-08-18:** 切换主题 [docsy](https://github.com/google/docsy) - **2024-07-14:** 启用 Zig 社区微信 2 群 - **2024-03-29:** 使用自定义域名 ziglang.cc - **2022-12-10:** 切换主题 [cupper-hugo](https://github.com/zwbetz-gh/cupper-hugo-theme) –> [hugo-xmin](https://github.com/yihui/hugo-xmin) - **2022-07-20:** 首次上线 - {{% /blocks/section %}} diff --git a/content/contributing.md b/content/contributing.smd similarity index 74% rename from content/contributing.md rename to content/contributing.smd index aa93554..739cacc 100644 --- a/content/contributing.md +++ b/content/contributing.smd @@ -1,13 +1,16 @@ --- -title: 如何贡献 -type: docs ---- +.title = "About", +.date = @date("1990-01-01T00:00:00"), +.author = "ZigCC", +.layout = "index.shtml", +.draft = false, +--- Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文群体中的使用,有多种方式可以参与进来: 1. 供稿,分享自己使用 Zig 的心得,方式见下文 2. 改进 zigcc 组织下的开源项目,这是 [open issues](https://github.com/search?q=state%3Aopen+org%3Azigcc++NOT+%E6%97%A5%E6%8A%A5&type=issues&ref=advsearch) -3. 参与不定期的[线上会议](/post/news/) +3. 参与不定期的线上会议 TODO # 供稿方式 @@ -15,7 +18,7 @@ Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文 2. 在 `content/post` 内添加自己的文章(md 或 org 格式均可),文件命名为: `${YYYY}-${MM}-${DD}-${SLUG}.md` 3. 文件开始需要包含一些描述信息,例如: -```plain +``` --- title: 欢迎 Zig 爱好者向本网站供稿 author: 刘家财 @@ -24,8 +27,10 @@ date: '2023-09-05T16:13:13+0800' ``` ## 本地预览 - -在写完文章后,可以使用 [Hugo](https://gohugo.io/) 进行本地预览,只需在项目根目录执行 `hugo server`,这会启动一个 HTTP 服务,默认的访问地址是: http://localhost:1313/ +TODO +```bash +zine +``` ## 发布平台 diff --git a/content/index.smd b/content/index.smd new file mode 100644 index 0000000..038e89d --- /dev/null +++ b/content/index.smd @@ -0,0 +1,20 @@ +--- +.title = "", +.date = @date("1990-01-01T00:00:00"), +.author = "ZigCC", +.layout = "index.shtml", +.draft = false, +--- + +[]($image.siteAsset('icons/zigcc.svg').id('logo')) + +Zig 中文社区致力于在中文用户中分享和传播 Zig 语言! + +我们是一群对 Zig 编程语言充满热情的开发者、学习者和爱好者。我们致力于: + +1. 分享 Zig 相关的知识和经验 +2. 翻译 Zig 官方文档和重要资源 +3. 组织线上线下的学习活动和讨论 +4. 推广 Zig 语言在中文开发者中的应用 + +无论你是 Zig 专家还是初学者,[我们都欢迎你的加入]($link.page('contributing'))! diff --git a/content/learn/preface.md b/content/learn/01-preface.smd similarity index 79% rename from content/learn/preface.md rename to content/learn/01-preface.smd index e161a4e..7ccdf4d 100644 --- a/content/learn/preface.md +++ b/content/learn/01-preface.smd @@ -1,6 +1,9 @@ --- -title: "前言" -weight: 1 +.title = "前言", +.date = @date("2024-01-01T00:00:00"), +.author = "Karl Seguin; ZigCC", +.layout = "learn.shtml", +.draft = false, --- 欢迎阅读 Zig 编程语言入门指南《学习 Zig》。本指南旨在让你轻松掌握 Zig。本指南假定你已有编程经验,语言不限。 diff --git a/content/learn/installing-zig.md b/content/learn/02-installing-zig.smd similarity index 90% rename from content/learn/installing-zig.md rename to content/learn/02-installing-zig.smd index 137cefd..35892b6 100644 --- a/content/learn/installing-zig.md +++ b/content/learn/02-installing-zig.smd @@ -1,6 +1,9 @@ --- -title: "安装 Zig" -weight: 2 +.title = "安装 Zig", +.date = @date("2024-01-01T00:00:00"), +.author = "Karl Seguin; ZigCC", +.layout = "learn.shtml", +.draft = false, --- > 原文地址: diff --git a/content/learn/language-overview-part1.md b/content/learn/03-language-overview-1.smd similarity index 96% rename from content/learn/language-overview-part1.md rename to content/learn/03-language-overview-1.smd index 241d15a..8164c8e 100644 --- a/content/learn/language-overview-part1.md +++ b/content/learn/03-language-overview-1.smd @@ -1,6 +1,9 @@ --- -title: "语言概述 - 第一部分" -weight: 3 +.title = "语言概述 - 第一部分", +.date = @date("2024-01-01T00:00:00"), +.author = "Karl Seguin; ZigCC", +.layout = "learn.shtml", +.draft = false, --- > 原文地址: @@ -32,9 +35,9 @@ pub const User = struct { 这是一个简单的示例,即使你是第一次看到 Zig,大概率能够看懂这段代码。尽管如此,下面的内容我们还是来逐行分析它。 -> 请参阅[安装 Zig 部分]({{< ref "installing-zig.md" >}}),以便快速启动并运行它。 +> 请参阅[安装 Zig 部分](02-installing-zig),以便快速启动并运行它。 -## 模块引用 +# [模块引用]($section.id('module-reference')) 很少有程序是在没有标准库或外部库的情况下以单个文件编写的。我们的第一个程序也不例外,它使用 Zig 的标准库来进行打印输出。 Zig 的模块系统非常简单,只依赖于 `@import` 函数和 `pub` 关键字(使代码可以在当前文件外部访问)。 @@ -85,7 +88,7 @@ const MAX_POWER = user.MAX_POWER; - 如何导入其他文件 - 如何导出变量、函数定义 -## 代码注释 +# [代码注释]($section.id('code-comment')) 下面这行 Zig 代码是一个注释: @@ -97,7 +100,7 @@ Zig 没有像 C 语言中类似 `/* ... */` 的多行注释。 基于注释的文档自动生成功能正在试验中。如果你看过 Zig 的标准库文档,你就会看到它的实际应用。`//!` 被称为顶级文档注释,可以放在文件的顶部。三斜线注释 (`///`) 被称为文档注释,可以放在特定位置,如声明之前。如果在错误的地方使用这两种文档注释,编译器都会出错。 -## 函数 +# [函数]($section.id('function')) 下面这行 Zig 代码是程序的入口函数 `main`: @@ -143,7 +146,7 @@ fn add(a: i64, b: i64) i64 { 为了提高可读性,Zig 中不支持函数重载(用不同的参数类型或参数个数定义的同名函数)。暂时来说,以上就是我们需要了解的有关函数的全部内容。 -## 结构体 +# [结构体]($section.id('struct')) 下面这行代码创建了一个 `User` 结构体: @@ -263,7 +266,7 @@ pub fn init(name: []const u8, power: u64) User { 就像我们迄今为止已经探索过的大多数东西一样,今后在讨论 Zig 语言的其他部分时,我们会再次讨论结构体。不过,在大多数情况下,它们都是简单明了的。 -## 数组和切片 +# [数组和切片]($section.id('array-slice')) 我们可以略过代码的最后一行,但鉴于我们的代码片段包含两个字符串 `"Goku"` 和 `{s}'s power is {d}\n`,你可能会对 Zig 中的字符串感到好奇。为了更好地理解字符串,我们先来了解一下数组和切片。 @@ -362,7 +365,7 @@ pub fn main() void { 在了解 Zig 语言的其他方面(尤其是字符串)的同时,我们还将发现更多有关数组和切片的知识。 -## 字符串 +# [字符串]($section.id('string')) 我希望我能说,Zig 里有字符串类型,而且非常棒。遗憾的是,它没有。最简单来说,字符串是字节(u8)的序列(即数组或切片)。实际上,我们可以从 `name` 字段的定义中看到这一点:`name: []const u8`. @@ -396,7 +399,7 @@ pub fn main() void { 当然,在实际程序中,大多数字符串(以及更通用的数组)在编译时都是未知的。最典型的例子就是用户输入,程序编译时并不知道用户输入。这一点我们将在讨论内存时再次讨论。但简而言之,对于这种在编译时不能确定值的数据(长度当然也就无从得知),我们将在运行时动态分配内存。我们的字符串变量(仍然是 `[]const u8` 类型)将是指向动态分配的内存的切片。 -## comptime 和 anytype +# [comptime 和 anytype]($section.id('comptime-anytype')) 在我们未解释的最后一行代码中,涉及的知识远比表面看到的多: @@ -444,6 +447,6 @@ pub fn main() void { struct{comptime year: comptime_int = 2023, comptime month: comptime_int = 8} ``` -在这里,我们给匿名结构的字段取名为 `year` 和 `month`。在原始代码中,我们没有这样做。在这种情况下,字段名会自动生成 0、1、2 等。虽然它们都是匿名结构字面形式的示例,但没有字段名称的结构通常被称为“元组”(tuple)。`print` 函数希望接收一个元组,并使用字符串格式中的序号位置来获取适当的参数。 +在这里,我们给匿名结构的字段取名为 `year` 和 `month`。在原始代码中,我们没有这样做。在这种情况下,字段名会自动生成 0、1、2 等。虽然它们都是匿名结构字面形式的示例,但没有字段名称的结构通常被称为"元组"(tuple)。`print` 函数希望接收一个元组,并使用字符串格式中的序号位置来获取适当的参数。 Zig 没有函数重载,也没有可变函数(vardiadic,具有任意数量参数的函数)。但它的编译器能根据传入的类型创建专门的函数,包括编译器自己推导和创建的类型。 diff --git a/content/learn/language-overview-part2.md b/content/learn/04-language-overview-2.smd similarity index 97% rename from content/learn/language-overview-part2.md rename to content/learn/04-language-overview-2.smd index 5c52b17..47d586c 100644 --- a/content/learn/language-overview-part2.md +++ b/content/learn/04-language-overview-2.smd @@ -1,13 +1,16 @@ --- -title: "语言概述 - 第二部分" -weight: 4 +.title = "语言概述 - 第二部分", +.date = @date("2024-01-01T00:00:00"), +.author = "Karl Seguin; ZigCC", +.layout = "learn.shtml", +.draft = false, --- > 原文地址: -本部分继续上一部分的内容:熟悉 Zig 语言。我们将探索 Zig 的控制流和结构以外的类型。通过这两部分的学习,我们将掌握 Zig 语言的大部分语法,这让我们可以继续深入 Zig 语言,同时也为如何使用 std 标准库打下了基础。 +本部分继续上一部分的内容:熟悉 Zig 语言。我们将探究 Zig 的控制流和结构以外的类型。通过这两部分的学习,我们将掌握 Zig 语言的大部分语法,这让我们可以继续深入 Zig 语言,同时也为如何使用 std 标准库打下了基础。 -## 控制流 +# [控制流]($section.id('kongzhi-liu')) Zig 的控制流很可能是我们所熟悉的,但它与 Zig 语言的其他特性协同工作是我们还没有探索过。我们先简单概述控制流的基本使用,之后在讨论依赖控制流的相关特性时,再来重新回顾。 @@ -204,7 +207,7 @@ const personality_analysis = blk: { 稍后,当我们讨论带标签的联合(tagged union)、错误联合(error unions)和可选类型(Optional)时,我们将看到控制流如何与它们联合使用。 -## 枚举 +# [枚举]($section.id('meiju')) 枚举是带有标签的整数常量。它们的定义很像结构体: @@ -238,7 +241,7 @@ const Stage = enum { `switch` 的穷举性质使它能与枚举很好地搭配,因为它能确保你处理了所有可能的情况。不过在使用 `switch` 的 `else` 子句时要小心,因为它会匹配任何新添加的枚举值,而这可能不是我们想要的行为。 -## 带标签的联合 Tagged Union +# [带标签的联合 Tagged Union]($section.id('tagged-union')) 联合定义了一个值可以具有的一系列类型。例如,这个 `Number` 可以是整数、浮点数或 nan(非数字): @@ -317,7 +320,7 @@ const Timestamp = union(enum) { 这里 Zig 会根据带标签的联合,自动创建一个隐式枚举。 -## 可选类型 Optional +# [可选类型 Optional]($section.id('optional')) 在类型前加上问号 `?`,任何值都可以声明为可选类型。可选类型既可以是 `null`,也可以是已定义类型的值: @@ -363,7 +366,7 @@ while (rows.next()) |row| { } ``` -## 未定义的值 Undefined +# [未定义的值 Undefined]($section.id('undefined')) 到目前为止,我们看到的每一个变量都被初始化为一个合理的值。但有时我们在声明变量时并不知道它的值。可选类型是一种选择,但并不总是合理的。在这种情况下,我们可以将变量设置为未定义,让其保持未初始化状态。 @@ -376,7 +379,7 @@ std.crypto.random.bytes(&pseudo_uuid); 上述代码仍然创建了一个 16 字节的数组,但它的每个元素都没有被赋值。 -## 错误 Errors +# [错误 Errors]($section.id('errors')) Zig 中错误处理功能十分简单、实用。这一切都从错误集(error sets)开始,错误集的使用方式类似于枚举: @@ -472,7 +475,7 @@ action(req, res) catch |err| return err; try action(req, res); ``` -鉴于必须处理错误,这一点尤其有用。多数情况下的做法就是使用 `try` 或 `catch`。 +鉴于必须处理错误,这一点尤其有用。多数情况下的做法就是使用 `try` 和 `catch`。 > Go 开发人员会注意到,`try` 比 `if err != nil { return err }` 的按键次数更少。 diff --git a/content/learn/style-guide.md b/content/learn/05-style-guide.smd similarity index 92% rename from content/learn/style-guide.md rename to content/learn/05-style-guide.smd index 02934f4..f5e9636 100644 --- a/content/learn/style-guide.md +++ b/content/learn/05-style-guide.smd @@ -1,13 +1,16 @@ --- -title: "编码风格" -weight: 5 +.title = "编码风格", +.date = @date("2024-01-01T00:00:00"), +.author = "Karl Seguin; ZigCC", +.layout = "learn.shtml", +.draft = false, --- > 原文地址: 本小节的主要内容是介绍 Zig 编译器强制遵守的 2 条规则,以及 Zig 标准库的命名惯例(naming convention)。 -## 未使用变量 Unused Variable +# [未使用变量 Unused Variable]($section.id('unused-variable')) Zig 编译器禁止`未使用变量`,例如以下代码会导致两处编译错误: @@ -52,7 +55,7 @@ fn add(a: i64, _: i64) i64 { 值得注意的是,在上述例子中,`std`也是一个未使用的符号,但是当前这种用法并不会导致任何编译错误。可能在未来,Zig 编译器也将此视为错误。 -## 变量覆盖 Variable Shadowing +# [变量覆盖 Variable Shadowing]($section.id('variable-shadowing')) Zig 不允许使用同名的变量。下面是一个读取 `socket` 的例子,这个例子包含了一个变量覆盖的编译错误: @@ -71,7 +74,7 @@ fn read(stream: std.net.Stream) ![]const u8 { 我认为,这个规范并不能使代码可读性提高。在这个场景下,应该是开发者,而不是编译器,更有资格选择更有可读性的命名方案。 -## 命名规范 +# [命名规范]($section.id('naming-convention')) 除了遵守以上这些规则以外,开发者可以自由地选择他们喜欢的命名规范。但是,理解 Zig 自身的命名规范是有益的,因为大部分你需要打交道的代码,如 Zig 标准库,或者其他三方库,都采用了 Zig 的命名规范。 diff --git a/content/learn/pointers.md b/content/learn/06-pointers.smd similarity index 95% rename from content/learn/pointers.md rename to content/learn/06-pointers.smd index 9e06c25..241083f 100644 --- a/content/learn/pointers.md +++ b/content/learn/06-pointers.smd @@ -1,6 +1,9 @@ --- -title: "指针" -weight: 6 +.title = "指针", +.date = @date("2024-01-01T00:00:00"), +.author = "Karl Seguin; ZigCC", +.layout = "learn.shtml", +.draft = false, --- > 原文地址: @@ -65,7 +68,7 @@ fn levelUp(user: User) void { 要理解这一点,我们可以将数据与内存联系起来,而变量只是将类型与特定内存位置关联起来的标签。例如,在 `main` 中,我们创建了一个`User`。内存中数据的简单可视化表示如下 -```text +``` user -> ------------ (id) | 1 | ------------ (power) @@ -84,7 +87,7 @@ user -> ------------ (id) 下面是一个稍有不同的可视化效果,其中包括内存地址。这些数据的起始内存地址是我想出来的一个随机地址。这是`user`变量引用的内存地址,也是第一个字段 `id` 的值所在的位置。由于 `id` 是一个 64 位整数,需要 8 字节内存。因此,`power` 必须位于 `$start_address + 8` 上: -```text +``` user -> ------------ (id: 1043368d0) | 1 | ------------ (power: 1043368d8) @@ -106,7 +109,7 @@ pub fn main() void { 这段代码输出了`user`、`user.id`、和`user.power`的地址。根据平台等差异,可能会得到不同的输出结果,但都会看到`user`和`user.id`的地址相同,而`user.power`的地址偏移量了 8 个字节。输出的结果如下: -```text +``` learning.User@1043368d0 u64@1043368d0 i32@1043368d8 @@ -184,11 +187,11 @@ pub const User = struct { 我们必须做两处改动。首先是用 `user` 的地址(即 `&user` )来调用 `levelUp`,而不是 `user`。这意味着我们的函数参数不再是 `User`,取而代之的是一个 `*User`,这是我们的第二处改动。 -我们不再需要通过 `user.power += 0;` 来强制修改 user 的那个丑陋的技巧了。最初,我们因为 user 是 var 类型而无法让代码编译,编译器告诉我们它从未被修改。我们以为编译器错了,于是通过强制修改来“糊弄”它。但正如我们现在所知道的,在 levelUp 中被修改的 user 是不同的;编译器是正确的。 +我们不再需要通过 `user.power += 0;` 来强制修改 user 的那个丑陋的技巧了。最初,我们因为 user 是 var 类型而无法让代码编译,编译器告诉我们它从未被修改。我们以为编译器错了,于是通过强制修改来"糊弄"它。但正如我们现在所知道的,在 levelUp 中被修改的 user 是不同的;编译器是正确的。 现在,代码已按预期运行。虽然在函数参数和内存模型方面仍有许多微妙之处,但我们正在取得进展。现在也许是一个好时机来说明一下,除了特定的语法之外,这些都不是 Zig 所独有的。我们在这里探索的模型是最常见的,有些语言可能只是向开发者隐藏了很多细节,因此也就隐藏了灵活性。 -## 方法 +# [方法]($section.id('fangfa')) 一般来说,我们会把 `levelUp` 写成 `User`结构的一个方法: @@ -207,7 +210,7 @@ pub const User = struct { 我最初选择函数是因为它很明确,因此更容易学习。 -## 常量函数参数 +# [常量函数参数]($section.id('const-func-param')) 我不止一次暗示过,在默认情况下,Zig 会传递一个值的副本(称为 "按值传递")。很快我们就会发现,实际情况要更微妙一些(提示:嵌套对象的复杂值怎么办?) @@ -219,11 +222,11 @@ pub const User = struct { > 也许你会想,即使与复制一个非常小的结构相比,通过引用传递怎么会更慢呢?我们接下来会更清楚地看到这一点,但要点是,当 `user` 是指针时,执行 `user.power` 会增加一点点开销。编译器必须权衡复制的代价和通过指针间接访问字段的代价。 -## 指向指针的指针 +# [指向指针的指针]($section.id('pointer-to-pointer')) 我们之前查看了`main`函数中 `user` 的内存结构。现在我们改变了 `levelUp`,那么它的内存会是什么样的呢? -```text +``` main: user -> ------------ (id: 1043368d0) <--- | 1 | | @@ -240,11 +243,7 @@ user -> ------------- (*User) | ------------- ``` -在 `levelUp` 中,`user` 是指向 `User` 的指针。它的值是一个地址。当然不是任何地址,而是 `main.user` 的地址。值得明确的是,`levelUp` 中的 `user` 变量代表一个具体的值。这个值恰好是一个地址。而且,它不仅仅是一个地址,还是一个类型,即 `*User`。这一切都非常一致,不管我们讨论的是不是指针:变量将类型信息与地址联系在一起。指针的唯一特殊之处在于,当我们使用点语法时,例如 `user.power`,Zig 知道 `user` 是一个指针,就会自动跟随地址。 - -> 通过指针访问字段时,有些语言可能会使用不同的运算符。 - -重要的是要理解,`levelUp`函数中的`user`变量本身存在于内存中的某个地址。就像之前所做的一样,我们可以亲自验证这一点: +在 `levelUp` 中,`user` 是指向 `User` 的指针。它的值是一个地址。当然不是任何地址,而是 `main.user` 的地址。值得明确的是,`levelUp` 中的 `user` 变量本身存在于内存中的某个地址。就像之前所做的一样,我们可以亲自验证这一点: ```zig fn levelUp(user: *User) void { @@ -259,7 +258,7 @@ fn levelUp(user: *User) void { 我们可以使用多级间接指针,但这并不是我们现在所需要的。本节的目的是说明指针并不特殊,它只是一个值,即一个地址和一种类型。 -## 嵌套指针 +# [嵌套指针]($section.id('nested-pointer')) 到目前为止,`User` 一直很简单,只包含两个整数。很容易就能想象出它的内存,而且当我们谈论『复制』 时,也不会有任何歧义。但是,如果 User 变得更加复杂并包含一个指针,会发生什么情况呢? @@ -273,7 +272,7 @@ pub const User = struct { 我们已经添加了`name`,它是一个切片。回想一下,切片由长度和指针组成。如果我们使用名字`Goku`初始化`user`,它在内存中会是什么样子? -```text +``` user -> ------------- (id: 1043368d0) | 1 | ------------- (power: 1043368d8) @@ -306,7 +305,7 @@ user -> ------------- (id: 1043368d0) 答案是只会进行浅拷贝。或者像有些人说的那样,只拷贝了变量可立即寻址的内存。这样看来,`levelUp` 可能只会得到一个 `user` 残缺副本,`name` 字段可能是无效的。但请记住,像 `user.name.ptr` 这样的指针是一个值,而这个值是一个地址。它的副本仍然是相同的地址: -```text +``` main: user -> ------------- (id: 1043368d0) | 1 | ------------- (power: 1043368d8) @@ -372,7 +371,7 @@ pub const User = struct { 不同编程语言有不同的实现方式,但许多语言的工作方式与此完全相同(或非常接近)。虽然所有这些看似深奥,但却是日常编程的基础。好消息是,你可以通过简单的示例和片段来掌握这一点;它不会随着系统其他部分复杂性的增加而变得更加复杂。 -## 递归结构 +# [递归结构]($section.id('recursive-struct')) 有时你需要一个递归结构。在保留现有代码的基础上,我们为 `User` 添加一个可选的 `manager` 字段,类型为 `?User`。同时,我们将创建两个`User`,并将其中一个指定为另一个的管理者: diff --git a/content/learn/stack-memory.md b/content/learn/07-stack-memory.smd similarity index 97% rename from content/learn/stack-memory.md rename to content/learn/07-stack-memory.smd index b23b564..a6df8f7 100644 --- a/content/learn/stack-memory.md +++ b/content/learn/07-stack-memory.smd @@ -1,6 +1,9 @@ --- -title: "栈内存" -weight: 7 +.title = "栈内存", +.date = @date("2024-01-01T00:00:00"), +.author = "Karl Seguin; ZigCC", +.layout = "learn.shtml", +.draft = false, --- > 原文地址: @@ -13,15 +16,15 @@ weight: 7 内存的第二个区域是调用栈,也是本小节的主题。第三个区域是堆,将在下一小节讨论。 -> 三块内存区域实际上没有真正的物理差别。操作系统和可执行文件创造了“内存区域”这个概念。 +> 三块内存区域实际上没有真正的物理差别。操作系统和可执行文件创造了"内存区域"这个概念。 -## 栈帧 +# 栈帧 迄今为止,我们所见的所有数据都是常量,存储在二进制的全局数据部分或作为局部变量。局部表示该变量只在其声明的范围内有效。在 Zig 中,范围从花括号开始到结束。大多数变量的范围限定在一个函数内,包括函数参数,或一个控制流块,比如 if。但是,正如所见,你可以创建任意块,从而创建任意范围。 在上一部分中,我们可视化了 `main` 和 `levelUp` 函数的内存,每个函数都有一个 User: -```text +``` main: user -> ------------- (id: 1043368d0) | 1 | ------------- (power: 1043368d8) @@ -56,7 +59,7 @@ levelUp: user -> ------------- (id: 1043368ec) | `levelUp` 紧接在 `main` 之后是有原因的:这是我们的简化版调用栈。当我们的程序启动时,`main` 及其局部变量被推入调用栈。当 `levelUp` 被调用时,它的参数和任何局部变量都会被添加到调用栈上。重要的是,当 `levelUp` 返回时,它会从栈中弹出。 在 `levelUp` 返回并且控制权回到 `main` 后,我们的调用栈如下所示: -```text +``` main: user -> ------------- (id: 1043368d0) | 1 | ------------- (power: 1043368d8) @@ -89,7 +92,7 @@ main: user -> ------------- (id: 1043368d0) 与全局数据一样,调用栈也由操作系统和可执行文件管理。程序启动时,以及此后启动的每个线程,都会创建一个调用栈(其大小通常可在操作系统中配置)。调用栈在程序的整个生命周期中都存在,如果是线程,则在线程的整个生命周期中都存在。程序或线程退出时,调用栈将被释放。我们的全局数据包含所有程序的全局数据,而调用栈只包含当前执行的函数层次的栈帧。这样做既能有效利用内存,又能简化堆栈帧的管理。 -## 悬空指针 +# 悬空指针 栈帧的简洁和高效令人惊叹。但它也很危险:当函数返回时,它的任何本地数据都将无法访问。这听起来似乎很合理,毕竟这是本地数据,但却会带来严重的问题。请看这段代码: diff --git a/content/learn/heap-memory-and-allocator.md b/content/learn/08-heap-memory.smd similarity index 98% rename from content/learn/heap-memory-and-allocator.md rename to content/learn/08-heap-memory.smd index 4dd9e31..8b7b879 100644 --- a/content/learn/heap-memory-and-allocator.md +++ b/content/learn/08-heap-memory.smd @@ -1,6 +1,9 @@ --- -title: "堆内存和分配器" -weight: 8 +.title = "堆内存", +.date = @date("2024-01-01T00:00:00"), +.author = "Karl Seguin; ZigCC", +.layout = "learn.shtml", +.draft = false, --- > 原文地址: @@ -11,7 +14,7 @@ weight: 8 本部分分为两个主题。第一个主题是第三个内存区域--堆的总体概述。另一个主题是 Zig 直接而独特的堆内存管理方法。即使你熟悉堆内存,比如使用过 C 语言的 `malloc`,你也会希望阅读第一部分,因为它是 Zig 特有的。 -## 堆 +# [堆]($section.id('dui')) 堆是我们可以使用的第三个也是最后一个内存区域。与全局数据和调用栈相比,堆有点像蛮荒之地:什么都可以使用。具体来说,在堆中,我们可以在运行时创建大小已知的内存,并完全控制其生命周期。 @@ -49,7 +52,7 @@ fn getRandomCount() !u8 { 一般来说,每次 `alloc` 都会有相应的 `free`。`alloc`分配内存,`free`释放内存。不要让这段简单的代码限制了你的想象力。这种 `try alloc` + `defer free` 的模式很常见,这是有原因的:在我们分配内存的地方附近释放相对来说是万无一失的。但同样常见的是在一个地方分配,而在另一个地方释放。正如我们之前所说,堆没有内置的生命周期管理。你可以在 HTTP 处理程序中分配内存,然后在后台线程中释放,这是代码中两个完全独立的部分。 -## defer 和 errdefer +# defer 和 errdefer 说句题外话,上面的代码介绍了一个新的语言特性:`defer`,它在退出作用域时执行给定的代码。『作用域退出』包括到达作用域的结尾或从作用域返回。严格来说, `defer` 与分配器或内存管理并无严格关系;你可以用它来执行任何代码。但上述用法很常见。 @@ -97,7 +100,7 @@ pub const Game = struct { > `init` 和 `deinit` 的名字并不特殊。它们只是 Zig 标准库使用的,也是社区采纳的名称。在某些情况下,包括在标准库中,会使用 `open` 和 `close`,或其他更适当的名称。 -## 双重释放和内存泄漏 +# 双重释放和内存泄漏 上面提到过,没有规则规定什么时候必须释放什么东西。但事实并非如此,还是有一些重要规则,只是它们不是强制的,需要你自己格外小心。 @@ -156,7 +159,7 @@ fn isSpecial(allocator: Allocator, name: [] const u8) !bool { 至少在双重释放的情况下,我们的程序会遭遇严重崩溃。内存泄漏可能很隐蔽。不仅仅是根本原因难以确定。真正的小泄漏或不常执行的代码中的泄漏甚至很难被发现。这是一个很常见的问题,Zig 提供了帮助,我们将在讨论分配器时看到。 -## 创建与销毁 +# 创建与销毁 `std.mem.Allocator`的`alloc`方法会返回一个切片,其长度为传递的第二个参数。如果想要单个值,可以使用 `create` 和 `destroy` 而不是 `alloc` 和 `free`。 @@ -230,7 +233,7 @@ fn init(allocator: std.mem.Allocator, id: u64, power: i32) !*User{ 请记住,`create` 返回一个 `!*User`,所以我们的 `user` 是 `*User` 类型。 -## 分配器 Allocator +# 分配器 Allocator Zig 的核心原则之一是无隐藏内存分配。根据你的背景,这听起来可能并不特别。但这与 C 语言中使用标准库的 malloc 函数分配内存的做法形成了鲜明的对比。在 C 语言中,如果你想知道一个函数是否分配内存,你需要阅读源代码并查找对 malloc 的调用。 @@ -251,7 +254,7 @@ defer allocator.free(say); 如果你正在构建一个库,那么最好接受一个 `std.mem.Allocator`,然后让库的用户决定使用哪种分配器实现。否则,你就需要选择正确的分配器,正如我们将看到的,这些分配器并不相互排斥。在你的程序中创建不同的分配器可能有很好的理由。 -## 通用分配器 GeneralPurposeAllocator +# 通用分配器 GeneralPurposeAllocator 顾名思义,`std.heap.GeneralPurposeAllocator` 是一种通用的、线程安全的分配器,可以作为应用程序的主分配器。对于许多程序来说,这是唯一需要的分配器。程序启动时,会创建一个分配器并传递给需要它的函数。我的 HTTP 服务器库中的示例代码就是一个很好的例子: @@ -296,7 +299,7 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 类型是什么,字段在哪里?类型其实是 `std.heap.general_purpose_allocator.Config`,但它并没有直接暴露出来,这也是我们没有显式给出类型的原因之一。没有设置字段是因为 Config 结构定义了默认值,我们将使用默认值。这是配置、选项的中常见的模式。事实上,我们在下面几行向 `init` 传递 `.{.port = 5882}` 时又看到了这种情况。在本例中,除了端口这一个字段外,我们都使用了默认值。 -## std.testing.allocator +# std.testing.allocator 希望当我们谈到内存泄漏时,你已经足够烦恼,而当我提到 Zig 可以提供帮助时,你肯定渴望了解更多这方面内容。这种帮助来自 `std.testing.allocator`,它是一个 `std.mem.Allocator` 实现。目前,它基于通用分配器(GeneralPurposeAllocator)实现,并与 Zig 的测试运行器进行了集成,但这只是实现细节。重要的是,如果我们在测试中使用 `std.testing.allocator`,就能捕捉到大部分内存泄漏。 @@ -420,7 +423,7 @@ self.allocator.free(self.items); 将`items`复制到我们的 `larger` 切片中后, 添加最后一行`free`可以解决泄漏的问题。如果运行 `zig test learning.zig`,便不会再有错误。 -## ArenaAllocator +# ArenaAllocator 通用分配器(GeneralPurposeAllocator)是一个合理的默认设置,因为它在所有可能的情况下都能很好地工作。但在程序中,你可能会遇到一些固定场景,使用更专业的分配器可能会更合适。其中一个例子就是需要在处理完成后丢弃的短期状态。解析器(Parser)通常就有这样的需求。一个 `parse` 函数的基本轮廓可能是这样的 @@ -505,7 +508,7 @@ defer list.deinit(); 最后举个简单的例子,我上面提到的 HTTP 服务器在响应中暴露了一个 `ArenaAllocator`。一旦发送了响应,它就会被清空。由于`ArenaAllocator`的生命周期可以预测(从请求开始到请求结束),因此它是一种高效的选择。就性能和易用性而言,它都是高效的。 -## 固定缓冲区分配器 FixedBufferAllocator +# 固定缓冲区分配器 FixedBufferAllocator 我们要讨论的最后一个分配器是 `std.heap.FixedBufferAllocator`,它可以从我们提供的缓冲区(即 `[]u8`)中分配内存。这种分配器有两大好处。首先,由于所有可能使用的内存都是预先创建的,因此速度很快。其次,它自然而然地限制了可分配内存的数量。这一硬性限制也可以看作是一个缺点。另一个缺点是,`free` 和 `destroy` 只对最后分配/创建的项目有效(想想堆栈)。调用释放非最后分配的内存是安全的,但不会有任何作用。 diff --git a/content/learn/generics.md b/content/learn/09-generics.smd similarity index 98% rename from content/learn/generics.md rename to content/learn/09-generics.smd index e3d8439..a65d260 100644 --- a/content/learn/generics.md +++ b/content/learn/09-generics.smd @@ -1,6 +1,9 @@ --- -title: "泛型" -weight: 9 +.title = "泛型", +.date = @date("2024-01-01T00:00:00"), +.author = "Karl Seguin; ZigCC", +.layout = "learn.shtml", +.draft = false, --- > 原文地址: diff --git a/content/learn/coding-in-zig.md b/content/learn/10-coding-in-zig.smd similarity index 97% rename from content/learn/coding-in-zig.md rename to content/learn/10-coding-in-zig.smd index bf12213..c209a02 100644 --- a/content/learn/coding-in-zig.md +++ b/content/learn/10-coding-in-zig.smd @@ -1,13 +1,16 @@ --- -title: "实战" -weight: 10 +.title = "实战", +.date = @date("2024-01-01T00:00:00"), +.author = "Karl Seguin; ZigCC", +.layout = "learn.shtml", +.draft = false, --- > 原文地址: 在介绍了 Zig 语言的大部分内容之后,我们将对一些主题进行回顾,并展示几种使用 Zig 编程时一些实用的技巧。在此过程中,我们将介绍更多的标准库,并介绍一些稍复杂些的代码片段。 -## 悬空指针 Dangling Pointers +# [悬空指针 Dangling Pointers]($section.id('dangling-pointers')) 我们首先来看看更多关于悬空指针的例子。这似乎是一个奇怪的问题,但如果你之前主要使用带垃圾回收的语言,这可能是你学习 Zig 最大的障碍。 @@ -102,7 +105,7 @@ const User = struct { 如果把所有东西都放在一个函数中,再加上一个像 `User` 这样的小值,这仍然像是一个人为制造的问题。我们需要一个能让数据所有权成为当务之急的例子。 -## 所有权 Ownership +# [所有权 Ownership]($section.id('ownership')) 我喜欢哈希表(HashMap),因为这是每个人都知道并且会经常使用的结构。它们有很多不同的用例,其中大部分你可能都用过。虽然哈希表可以用在一个短期查找的地方,但通常用于长期查找,因此插入其内的值需要同样长的生命周期。 @@ -215,7 +218,7 @@ defer { 我保证,关于悬挂指针和内存管理的讨论已经结束了。我们所讨论的内容可能还不够清晰或过于抽象。当你有更实际的问题需要解决时,再重新讨论这个问题也不迟。不过,如果你打算编写任何稍具规模(non-trivial)的程序,这几乎肯定是你需要掌握的内容。当你觉得可以的时候,我建议你参考上面这个示例,并自己动手实践一下。引入一个 `UserLookup` 类型来封装我们必须做的所有内存管理。尝试使用 `*User` 代替 `User`,在堆上创建用户,然后像处理键那样释放它们。编写覆盖新结构的测试,使用 `std.testing.allocator` 确保不会泄漏任何内存。 -## ArrayList +# [ArrayList]($section.id('arraylist')) 现在你可以忘掉我们的 `IntList` 和我们创建的通用替代方案了。Zig 标准库中有一个动态数组实现:`std.ArrayList(T)`。 @@ -310,9 +313,9 @@ pub fn main() !void { } ``` -## Anytype +# [Anytype]($section.id('anytype')) -在[语言概述的第一部分](02-language-overview-part1.md)中,我们简要介绍了 `anytype`。这是一种非常有用的编译时 duck 类型。下面是一个简单的 logger: +在[语言概述的第一部分](03-language-overview-1)中,我们简要介绍了 `anytype`。这是一种非常有用的编译时 duck 类型。下面是一个简单的 logger: ```zig pub const Logger = struct { @@ -380,7 +383,7 @@ fn stringify( 第一个参数 `value: anytype` 是显而易见的,它是要序列化的值,可以是任何类型(实际上,Zig 的 JSON 序列化器不能序列化某些类型,比如 HashMap)。我们可以猜测,`out_stream` 是写入 JSON 的地方,但至于它需要实现什么方法,你和我一样猜得到。唯一的办法就是阅读源代码,或者传递一个假值,然后使用编译器错误作为我们的文档。如果有更好的自动文档生成器,这一点可能会得到改善。不过,我希望 Zig 能提供接口,这已经不是第一次了。 -## @TypeOf +# [@TypeOf]($section.id('typeof')) 在前面的部分中,我们使用 `@TypeOf` 来帮助我们检查各种变量的类型。从我们的用法来看,你可能会认为它返回的是字符串类型的名称。然而,鉴于它是一个 PascalCase 风格函数,你应该更清楚:它返回的是一个 `type`。 @@ -409,7 +412,7 @@ pub const User = struct { 更常见的是 `@TypeOf` 与 `@typeInfo` 配对,后者返回一个 `std.builtin.Type`。这是一个功能强大的带标签的联合(tagged union),可以完整描述一个类型。`std.json.stringify` 函数会递归地调用它,以确定如何将提供的 `value` 序列化。 -# 构建系统 +# [构建系统]($section.id('build-system')) 如果你通读了整本指南,等待着深入了解如何建立更复杂的项目,包括多个依赖关系和各种目标,那你就要失望了。Zig 拥有强大的构建系统,以至于越来越多的非 Zig 项目都在使用它,比如 libsodium。不幸的是,所有这些强大的功能都意味着,对于简单的需求来说,它并不是最容易使用或理解的。 @@ -494,7 +497,7 @@ test "dummy build test" { 这是启动和运行构建系统所需的最低配置。但是请放心,如果你需要构建你的程序,Zig 内置的功能大概率能覆盖你的需求。最后,你可以(也应该)在你的项目根目录下使用 `zig init`,让 Zig 为你创建一个文档齐全的 `build.zig` 文件。 -## 第三方依赖 +# [第三方依赖]($section.id('third-party-deps')) Zig 的内置软件包管理器相对较新,因此存在一些缺陷。虽然还有改进的余地,但它目前还是可用的。我们需要了解两个部分:创建软件包和使用软件包。我们将对其进行全面介绍。 diff --git a/content/learn/conclusion.md b/content/learn/11-conclusion.smd similarity index 93% rename from content/learn/conclusion.md rename to content/learn/11-conclusion.smd index 1f9400b..8f35a2b 100644 --- a/content/learn/conclusion.md +++ b/content/learn/11-conclusion.smd @@ -1,6 +1,9 @@ --- -title: "总结" -weight: 11 +.title = "总结", +.date = @date("2024-01-01T00:00:00"), +.author = "Karl Seguin; ZigCC", +.layout = "learn.shtml", +.draft = false, --- > 原文总结: diff --git a/content/learn/_index.md b/content/learn/index.smd similarity index 77% rename from content/learn/_index.md rename to content/learn/index.smd index db72046..1ba68c8 100644 --- a/content/learn/_index.md +++ b/content/learn/index.smd @@ -1,10 +1,27 @@ --- -title: Learning Zig 中文翻译 -type: docs -cascade: - - type: docs +.title = "Learning Zig 中文翻译", +.date = @date("2024-01-01T00:00:00"), +.author = "Karl Seguin; ZigCC", +.layout = "page.shtml", +.draft = false, --- +## [Section Contents]($section.id('table-of-contents')) + +- [前言](./01-preface) +- [安装 Zig](./02-installing-zig) +- [语言概述 - 第一部分](./03-language-overview-1) +- [语言概述 - 第二部分](./04-language-overview-2) +- [编码风格](./05-style-guide) +- [指针](./06-pointers) +- [栈内存](./07-stack-memory) +- [堆内存和分配器](./08-heap-memory) +- [泛型](./09-generics) +- [实战](./10-coding-in-zig) +- [总结](./11-conclusion) + +## 牟言 + [《学习 Zig》](https://www.openmymind.net/learning_zig/)系列教程最初由 [Karl Seguin](https://github.com/karlseguin) 编写,该教程行文流畅,讲述的脉络由浅入深,深入浅出,是入门 Zig 非常不错的选择。因此,[Zig 中文社区](https://ziglang.cc)将其翻译成中文,便于在中文用户内阅读与传播。 初次接触 Zig 的用户可以按序号依次阅读,对于有经验的 Zig 开发者可按需阅读感兴趣的章节。 @@ -29,7 +46,10 @@ cascade: 在本仓库的 [release 页面](https://github.com/zigcc/zigcc.github.io/releases)会定期将本教程导出为 PDF 格式,读者可按需下载。 -读者也可以使用右侧导航栏中的『[整节打印](_print)』将当前版本教程保存为 PDF 格式。 +```html + + +``` ## 其他学习资料 diff --git a/content/monthly/202207.md b/content/monthly/202207.smd similarity index 90% rename from content/monthly/202207.md rename to content/monthly/202207.smd index 4f5a6c7..0c652d5 100644 --- a/content/monthly/202207.md +++ b/content/monthly/202207.smd @@ -1,6 +1,9 @@ --- -title: "202207 | 开刊 HelloWorld" -date: 2022-07-30T14:08:15+0800 +.title = "202207 | 开刊 HelloWorld", +.date = @date("2024-01-01T00:00:00"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, --- # 观点/教程 @@ -27,7 +30,7 @@ date: 2022-07-30T14:08:15+0800 - [zbor - a CBOR en-/ decoder - Zig NEWS ⚡](https://zig.news/r4gus/zbor-a-cbor-en-decoder-2di0) - [How I built zig-sqlite](https://rischmann.fr/blog/how-i-built-zig-sqlite) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-07-01..2022-08-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2022-07-01..2022-08-01) - [std 文档展示更全面](https://twitter.com/croloris/status/1550955321694330880) - [New Autodocs! by kristoff-it · Pull Request #12173 · ziglang/zig](https://github.com/ziglang/zig/pull/12173) diff --git a/content/monthly/202208.md b/content/monthly/202208.smd similarity index 95% rename from content/monthly/202208.md rename to content/monthly/202208.smd index b036c4c..065b726 100644 --- a/content/monthly/202208.md +++ b/content/monthly/202208.smd @@ -1,6 +1,9 @@ --- -title: 202208 | stage2 默认开启 -date: 2022-08-28T16:03:12+0800 +.title = "202208 | stage2 默认开启", +.date = @date("2024-01-01T00:00:00"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, --- # 观点/教程 @@ -25,7 +28,7 @@ date: 2022-08-28T16:03:12+0800 ![](/images/blockly.webp) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-08-01..2022-09-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2022-08-01..2022-09-01) - [make self-hosted the default compiler by andrewrk · Pull Request #12368](https://github.com/ziglang/zig/pull/12368) Master 分支默认已经是 stage2,这是个里程碑的事情,作者为了用户平滑升级,还写了份[升级指南](https://github.com/ziglang/zig/wiki/Self-Hosted-Compiler-Upgrade-Guide)。优势就是:速度更快、内存占用更少、错误信息更加友好。其他相关改动: diff --git a/content/monthly/202209.md b/content/monthly/202209.smd similarity index 94% rename from content/monthly/202209.md rename to content/monthly/202209.smd index bc5902d..2e006ce 100644 --- a/content/monthly/202209.md +++ b/content/monthly/202209.smd @@ -1,6 +1,9 @@ --- -title: 202209 | 锋芒毕露 -date: 2022-10-03T23:32:12+0800 +.title = "202209 | 锋芒毕露", +.date = @date("2024-01-01T00:00:00"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, --- # Zig VS Rust 火花 @@ -14,7 +17,7 @@ date: 2022-10-03T23:32:12+0800 ### 时间线 - 8/26 号,一篇关于 wasm 2 Game Jam 的[分析报告](https://wasm4.org/blog/jam-2-results/)中,使用 Zig 的人数最多 -- 9/9 号,这篇报告在 HackerNews 上引起了[热烈讨论](https://news.ycombinator.com/item?id=32777636),其中 Walton 在多处回复中表示 Zig 语言的劣势,[并称](https://news.ycombinator.com/item?id=32782842) +- 9/9 号,这篇报告在 HackerNews 上引起了[热烈讨论](https://news.ycombinator.com/item?id`32777636),其中 Walton 在多处回复中表示 Zig 语言的劣势,[并称](https://news.ycombinator.com/item?id`32782842) > It's perfectly reasonable to take the position that it's deeply problematic for a language aiming for wide use in 2022 to not be memory safe. There's no requirement that you "focus on tradeoffs", especially since real people get hurt by memory safety problems. - Loris [回复到](https://news.ycombinator.com/item?id=32785380): > I think you're actively hurting the project that you care about in your ineffective crusade, but hey, don't let me stop you. @@ -27,7 +30,7 @@ date: 2022-10-03T23:32:12+0800 > This is wrong because you can still have UAF from freed stack frames. - Loris 针对 Walton 的回复说了句“What a boring, useless take.([原推](https://mobile.twitter.com/kripken/status/1568428308131622913)的回复已经被 Loris 删除了,可以[在这里](https://archive.ph/jq3kw#selection-1275.0-1275.30)看到历史): - Walton 发推[表示](https://mobile.twitter.com/pcwalton/status/1568302065851707392)在 2022 年,所有语言都应该是内存安全,应该算是『编程语言界的共识』,并称 Zig 是行业的一大退步 😅 -- Loris 专门发了一个 [Twitter thread](https://twitter.com/croloris/status/1568573729940164608?s=21&t=v2Dj_F2f_kUzZDQps5KjtQ) 来阐述『软件的目标不仅仅是内存安全,更重要的是正确』。比如 tigerbeetle 这里[提到的](https://github.com/tigerbeetledb/tigerbeetle/blob/main/docs/DESIGN.md)。而且即便内存安全,也可能发生 OOM +- Loris 专门发了一个 [Twitter thread](https://twitter.com/croloris/status/1568573729940164608?s`21&t`v2Dj_F2f_kUzZDQps5KjtQ) 来阐述『软件的目标不仅仅是内存安全,更重要的是正确』。比如 tigerbeetle 这里[提到的](https://github.com/tigerbeetledb/tigerbeetle/blob/main/docs/DESIGN.md)。而且即便内存安全,也可能发生 OOM ### 总结 @@ -47,7 +50,7 @@ date: 2022-10-03T23:32:12+0800 - [Building a Tiny Mutex](https://zig.news/kprotty/building-a-tiny-mutex-537k) - [Perfecting WebGPU/Dawn native graphics for Zig](https://devlog.hexops.com/2022/perfecting-webgpu-native/) - [Cross-Compiling and packaging C, Go and Zig projects with Nix](https://flyx.org/cross-packaging/) 介绍如何基于 [Nix](https://nixos.org/) 来进行交叉编译 -- [Revisiting the design approach to the Zig programming language](https://about.sourcegraph.com/blog/zig-programming-language-revisiting-design-approach) Sourcegraph 的一档播客,对 Zig 创始人的采访,介绍了 Zig 的由来,其中提到一个性能优化点是:untagged union,这里有它的一些介绍:[Andrew Kelley claims Zig is faster than Rust in perfomance](https://www.reddit.com/r/rust/comments/s5caye/comment/hsz6uf0/?utm_source=share&utm_medium=web2x&context=3) +- [Revisiting the design approach to the Zig programming language](https://about.sourcegraph.com/blog/zig-programming-language-revisiting-design-approach) Sourcegraph 的一档播客,对 Zig 创始人的采访,介绍了 Zig 的由来,其中提到一个性能优化点是:untagged union,这里有它的一些介绍:[Andrew Kelley claims Zig is faster than Rust in perfomance](https://www.reddit.com/r/rust/comments/s5caye/comment/hsz6uf0/?utm_source`share&utm_medium`web2x&context=3) - [Zig ⚡ Improving the User Experience for Unused Variables](https://vimeo.com/748218307) # 项目/工具 diff --git a/content/monthly/202210.md b/content/monthly/202210.smd similarity index 86% rename from content/monthly/202210.md rename to content/monthly/202210.smd index c1c5598..ba309a4 100644 --- a/content/monthly/202210.md +++ b/content/monthly/202210.smd @@ -1,6 +1,9 @@ --- -title: 202210 | 0.10 蓄势待发 -date: 2022-10-30T10:10:14+0800 +.title = "202210 | 0.10 蓄势待发", +.date = @date("2024-01-01T00:00:00"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, --- # 观点/教程 @@ -13,8 +16,8 @@ date: 2022-10-30T10:10:14+0800 - [Building a high-performance database buffer pool in Zig using io_uring's new fixed-buffer mode](https://gavinray97.github.io/blog/io-uring-fixed-bufferpool-zig) - [Zig-style generics are not well-suited for most languages](https://typesanitizer.com/blog/zig-generics.html) - [Zig and WebAssembly are a match made in heaven](https://blog.battlefy.com/zig-and-webassembly-are-a-match-made-in-heaven) -- [视频] [Stay Together For The Kids - Andrew Kelley - Software You Can Love 2022](https://www.bilibili.com/video/BV1ne411G74c/?share_source=copy_web&vd_source=9191359325bcbfb53bd116d1f5b22175) -- [视频] [Ziglibc: Sweeping out the rug from underneath C - Jonathan Marler - Software You Can Love 2022](https://www.bilibili.com/video/BV1Td4y1y7U9/?share_source=copy_web&vd_source=9191359325bcbfb53bd116d1f5b22175) +- [视频] [Stay Together For The Kids - Andrew Kelley - Software You Can Love 2022](https://www.bilibili.com/video/BV1ne411G74c/?share_source`copy_web&vd_source`9191359325bcbfb53bd116d1f5b22175) +- [视频] [Ziglibc: Sweeping out the rug from underneath C - Jonathan Marler - Software You Can Love 2022](https://www.bilibili.com/video/BV1Td4y1y7U9/?share_source`copy_web&vd_source`9191359325bcbfb53bd116d1f5b22175) - [音频] [005. 与 LemonHX 畅聊新一代编程语言 Zig – RustTalk](https://rusttalk.github.io/podcast/005/) # 项目/工具 @@ -26,4 +29,4 @@ date: 2022-10-30T10:10:14+0800 - 一个 Zig Hackers 的 Twitter 列表:[Zig Hero](https://twitter.com/i/lists/1570249876155568129) - [A minimal RocksDB example with Zig](https://notes.eatonphil.com/zigrocks.html) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-10-01..2022-11-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2022-10-01..2022-11-01) diff --git a/content/monthly/202211.md b/content/monthly/202211.smd similarity index 93% rename from content/monthly/202211.md rename to content/monthly/202211.smd index 83b9659..de49e41 100644 --- a/content/monthly/202211.md +++ b/content/monthly/202211.smd @@ -1,6 +1,9 @@ --- -title: 202211 | 0.10 横空出世 -date: 2022-12-04T18:45:34+0800 +.title = "202211 | 0.10 横空出世", +.date = @date("2024-01-01T00:00:00"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, --- # [0.10.0 Release Notes](https://ziglang.org/download/0.10.0/release-notes.html) @@ -16,7 +19,7 @@ date: 2022-12-04T18:45:34+0800 欢迎喜欢 Zig 的小伙伴加入! {{< figure src="https://github.com/zigcc/.github/raw/main/weixin.jpg" - width="200" title="ZigCC 微信群二维码" >}} + width`"200" title`"ZigCC 微信群二维码" >}} # 观点/教程 @@ -58,4 +61,4 @@ const Animal = union(enum){ - [trace.zig: A small and simple tracing client library](https://zig.news/huntrss/tracezig-a-small-and-simple-tracing-client-library-2ffj),项目地址:[Zig tracing / trace.zig](https://gitlab.com/zig_tracing/trace.zig) - [Zig Support plugin for IntelliJ and CLion version 0.2.0 released](https://zig.news/marioariasc/zig-support-plugin-for-intellij-and-clion-version-020-released-3g06) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-11-01..2022-12-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2022-11-01..2022-12-01) diff --git a/content/monthly/202212.md b/content/monthly/202212.md deleted file mode 100644 index 6183e05..0000000 --- a/content/monthly/202212.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: 202212 | TBD -date: 2022-12-11T14:11:38+0800 -draft: true ---- - -# 观点/教程 - -- [Goodbye to the C++ Implementation of Zig ⚡ Zig Programming Language](https://ziglang.org/news/goodbye-cpp/) - -# 项目/工具 - -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-12-01..2023-01-01) diff --git a/content/monthly/202212.smd b/content/monthly/202212.smd new file mode 100644 index 0000000..efd16e7 --- /dev/null +++ b/content/monthly/202212.smd @@ -0,0 +1,15 @@ +--- +.title = "202212 | TBD", +.date = @date("2024-01-01T00:00:00"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +--- + +# 观点/教程 + +- [Goodbye to the C++ Implementation of Zig ⚡ Zig Programming Language](https://ziglang.org/news/goodbye-cpp/) + +# 项目/工具 + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2022-12-01..2023-01-01) diff --git a/content/monthly/202301.org b/content/monthly/202301.org deleted file mode 100644 index 70768ac..0000000 --- a/content/monthly/202301.org +++ /dev/null @@ -1,63 +0,0 @@ -#+TITLE: 202301 | 包管理来了 -#+DATE: 2023-01-31T20:05:19+0800 -#+LASTMOD: 2023-01-31T20:05:19+0800 - -* [[https://ziglang.org/download/0.10.1/release-notes.html][0.10.1]] 版本发布 -一个小版本,主要是 bugfix。最主要的功能是:[[https://github.com/ziglang/zig/pull/14265][Package Manager MVP]],Zig 终于开始支持包管理了! -不过才刚刚开始,有一个[[https://github.com/ziglang/zig/projects/4][面板]]来跟踪相关 issue 进度。使用的配置文件是 =build.zig.ini= ,格式如下: -#+begin_src conf -[package] -name=libffmpeg -version=5.1.2 - -[dependency] -name=libz -url=https://github.com/andrewrk/libz/archive/f0e53cc2391741034b144a2c2076ed8a9937b29b.tar.gz -hash=c9b30cffc40999d2c078ff350cbcee642970a224fe123c756d0892f876cf1aae - -[dependency] -name=libmp3lame -url=https://github.com/andrewrk/libmp3lame/archive/497568e670bfeb14ab6ef47fb6459a2251358e43.tar.gz -hash=9ba4f49895b174a3f918d489238acbc146bd393575062b2e3be33488b688e36f -#+end_src -=build.zig= 引用方式: -#+begin_src zig -const std = @import("std"); - -pub fn build(b: *std.build.Builder) void { - const target = b.standardTargetOptions(.{}); - const mode = b.standardReleaseOptions(); - - const libz_dep = b.dependency("libz", .{}); - const libmp3lame_dep = b.dependency("libmp3lame", .{}); - - const lib = b.addStaticLibrary("ffmpeg", null); - lib.setTarget(target); - lib.setBuildMode(mode); - lib.linkLibrary(libz_dep.artifact("z")); - lib.linkLibrary(libmp3lame_dep.artifact("mp3lame")); - lib.linkLibC(); - lib.addIncludePath("."); - lib.install(); -} -#+end_src -其他关注点: -- LLVM 升级到 [[http://releases.llvm.org/15.0.7/docs/ReleaseNotes.html][15.0.7]] -- 是 0.10.x 的最后一个 release 版本 -* 观点/教程 -- [[https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj][Code study: interface idioms/patterns in zig standard libraries]] :: - 由于 Zig 目前还不支持接口抽空,本文介绍了标准库中来实现类似功能的五种方式 -- [[https://kihlander.net/post/a-zig-diary/][A Zig Diary]] :: 作者分享了对 Zig 的使用体验 -- [[https://datastackshow.com/podcast/why-accounting-needs-its-own-database-with-joran-greef-of-tiger-beetle/][Why Accounting Needs Its Own Database with Joran Greef of Tiger Beetle]] :: 播客分享 -- [[https://0110.be/posts/Crossplatform_JNI_builds_with_Zig][Crossplatform JNI builds with Zig]] :: 又一个使用 Zig 作为交叉编译的例子 -* 项目/工具 -- [[https://zig.news/renerocksai/introducing-zap-blazingly-fast-backends-in-zig-3jhh][Introducing ⚡zap⚡ - blazingly fast backends in zig]] :: - Zap 是 Zig 对 [[https://facil.io/][facil.io - The C Web Application Framework]] 的封装,本文算是对它的宣传。 -- [[https://zig.news/auguste/indexing-every-zig-for-great-justice-4l1h][Indexing every Zig for great justice]] :: - 本文介绍了另一种语言服务器协议(LSP):SCIP,并用 zig 实现。项目处于早期阶段。 -- [[https://github.com/dantecatalfamo/zig-git][dantecatalfamo/zig-git]] :: - Implementing git structures and functions in zig -- [[https://github.com/axiomhq/zig-hyperloglog][axiomhq/zig-hyperloglog]] :: Zig library for HyperLogLog estimation -- [[https://thisweekinzig.mataroa.blog/][This Week In Zig]] :: 一个介绍 Zig 的周刊,主要是 master 分支上的改动 - -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-01-01..2023-02-01][Zig 语言更新]] diff --git a/content/monthly/202301.smd b/content/monthly/202301.smd new file mode 100644 index 0000000..8cfdf88 --- /dev/null +++ b/content/monthly/202301.smd @@ -0,0 +1,67 @@ +--- +.title = "202301 | 包管理来了", +.date = @date("2023-01-31T20:05:19+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2023-01-31T20:05:19+0800" }, +--- + +# [0.10.1 版本发布]($section.id('0.10.1 版本发布')) +一个小版本,主要是 bugfix。最主要的功能是:[Package Manager MVP](https://github.com/ziglang/zig/pull/14265),Zig 终于开始支持包管理了! +不过才刚刚开始,有一个[面板](https://github.com/ziglang/zig/projects/4)来跟踪相关 issue 进度。使用的配置文件是 `build.zig.ini` ,格式如下: +```conf +[package] +name=libffmpeg +version=5.1.2 + +[dependency] +name=libz +url=https://github.com/andrewrk/libz/archive/f0e53cc2391741034b144a2c2076ed8a9937b29b.tar.gz +hash=c9b30cffc40999d2c078ff350cbcee642970a224fe123c756d0892f876cf1aae + +[dependency] +name=libmp3lame +url=https://github.com/andrewrk/libmp3lame/archive/497568e670bfeb14ab6ef47fb6459a2251358e43.tar.gz +hash=9ba4f49895b174a3f918d489238acbc146bd393575062b2e3be33488b688e36f +``` +`build.zig` 引用方式: +```zig +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const mode = b.standardReleaseOptions(); + + const libz_dep = b.dependency("libz", .{}); + const libmp3lame_dep = b.dependency("libmp3lame", .{}); + + const lib = b.addStaticLibrary("ffmpeg", null); + lib.setTarget(target); + lib.setBuildMode(mode); + lib.linkLibrary(libz_dep.artifact("z")); + lib.linkLibrary(libmp3lame_dep.artifact("mp3lame")); + lib.linkLibC(); + lib.addIncludePath("."); + lib.install(); +} +``` +其他关注点: +- LLVM 升级到 [15.0.7](http://releases.llvm.org/15.0.7/docs/ReleaseNotes.html) +- 是 0.10.x 的最后一个 release 版本 + +# [观点/教程]($section.id('观点/教程')) +- [Code study: interface idioms/patterns in zig standard libraries](https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj) :: 由于 Zig 目前还不支持接口抽空,本文介绍了标准库中来实现类似功能的五种方式 +- [A Zig Diary](https://kihlander.net/post/a-zig-diary/) :: 作者分享了对 Zig 的使用体验 +- [Why Accounting Needs Its Own Database with Joran Greef of Tiger Beetle](https://datastackshow.com/podcast/why-accounting-needs-its-own-database-with-joran-greef-of-tiger-beetle/) :: 播客分享 +- [Crossplatform JNI builds with Zig](https://0110.be/posts/Crossplatform_JNI_builds_with_Zig) :: 又一个使用 Zig 作为交叉编译的例子 + +# [项目/工具]($section.id('项目/工具')) +- [Introducing ⚡zap⚡ - blazingly fast backends in zig](https://zig.news/renerocksai/introducing-zap-blazingly-fast-backends-in-zig-3jhh) :: Zap 是 Zig 对 [facil.io - The C Web Application Framework](https://facil.io/) 的封装,本文算是对它的宣传。 +- [Indexing every Zig for great justice](https://zig.news/auguste/indexing-every-zig-for-great-justice-4l1h) :: 本文介绍了另一种语言服务器协议(LSP):SCIP,并用 zig 实现。项目处于早期阶段。 +- [dantecatalfamo/zig-git](https://github.com/dantecatalfamo/zig-git) :: Implementing git structures and functions in zig +- [axiomhq/zig-hyperloglog](https://github.com/axiomhq/zig-hyperloglog) :: Zig library for HyperLogLog estimation +- [This Week In Zig](https://thisweekinzig.mataroa.blog/) :: 一个介绍 Zig 的周刊,主要是 master 分支上的改动 + +# [Zig 语言更新]($section.id('Zig 语言更新')) +- [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2023-01-01..2023-02-01) diff --git a/content/monthly/202302.org b/content/monthly/202302.org deleted file mode 100644 index 4bd6cad..0000000 --- a/content/monthly/202302.org +++ /dev/null @@ -1,64 +0,0 @@ -#+TITLE: 202302 | 精益求精的包管理 -#+DATE: 2023-02-26T17:36:12+0800 -#+LASTMOD: 2023-01-31T20:05:19+0800 - -* 包管理器进展 -包管理器自 [[https://github.com/ziglang/zig/pull/14265][#14265]] 合并后一直在不断推进,以下两个是最主要的改变: -- [[https://github.com/ziglang/zig/issues/14307][build system terminology update: package, project, module, dependency]] - - 这里重新梳理了现在的术语,主要有以下几个: - - =package= 文件的集合,由文件的 hash 值唯一指定,一个 package 可能包含任意数目的 compilation artifacts 与 modules。 - - =dependency= 不同 package 之间的有向边,一个 package 可以有任意个依赖,一个 package 也可以用作任意项目的依赖 - - =module= 文件的集合,每一个模块都有一个 root 文件,在被 =@import= 时用到。 - - =compilation artifact= 编译构建产物,可以是 static library,dynamic library,an executable 或 an object file,对应之前版本的 =LibExeObjStep= -- [[https://github.com/ziglang/zig/pull/14523][introduce Zig Object Notation and use it for the build manifest file (build.zig.zon)]] - - 使用 zon 格式替代之前的 ini,格式如下: - #+begin_src zig -.{ - .name = "awesome-cli", - .version = "0.1.0", - .dependencies = .{ - .simargs = .{ - .url = "https://github.com/jiacai2050/simargs/archive/0a1a2afd072cc915009a063075743192fc6b1fd5.tar.gz", - .hash = "1220a6554eccb2e9a9d7d63047e062314851ffd11315b9e6d1b5e06a9dde3275f150", - }, - }, -} - #+end_src - 一些使用了包管理的实际项目: - - [[https://github.com/andrewrk/ffmpeg][andrewrk/ffmpeg: ffmpeg with the build system replaced by zig]] - - [[https://github.com/jiacai2050/loc][jiacai2050/loc: Lines of code in Zig]],适配包管理的相关 [[https://github.com/jiacai2050/loc/commit/7b01c09a4ba9d3ddc3d067cc6af654601a99035a][commit 修改]] - - [[https://github.com/PCRE2Project/pcre2/pull/206][PCRE2Project/pcre2: zig build support]] - - [[https://github.com/nikneym/ws][nikneym/ws: WebSocket library for Zig]] - - [[https://github.com/natecraddock/ziglua/issues/6][Zig package manager · Issue #6 · natecraddock/ziglua]] - -也欢迎大家给自己熟悉的 C/C++ 项目提 PR 让其支持 zig build,让构建不再那么痛苦。 -* 观点/教程 -- [[https://matklad.github.io/2023/02/10/how-a-zig-ide-could-work.html][How a Zig IDE Could Work]] -- [[https://devlog.hexops.com/2023/zig-0-11-breaking-build-changes/][Zig tips: v0.11 std.build API / package manager changes | Hexops' devlog]] -- [[https://lobste.rs/s/zh3ulk/pcre2_support_zig_build][pcre2 support zig build]] -- [[https://zig.news/andrewrk/multi-object-for-loops-data-oriented-design-41ob][Multi-Object For Loops + Struct-Of-Arrays]] -- [[https://kristoff.it/blog/zig-multi-sequence-for-loops/][Zig's Curious Multi-Sequence For Loops]],[[https://lobste.rs/s/ihf30a/zig_s_curious_multi_sequence_for_loops][Lobster 评论]] - 上面这两篇的文章都是演示了最新的 for 语法,开始支持了 range: - #+begin_src zig -for (0..4) |n| { - std.debug.print("{} ", .{n}); -} - #+end_src - 同时也支持了一次性迭代多个数组的功能: - #+begin_src zig - -var elems = [_][]const u8 { "water", "earth", "fire", "wind" }; -var nats = [_][]const u8 { "tribes", "kingdom", "nation", "nomads" }; - -for (elems, nats) |e, n| { - std.debug.print("{s} {s}\n", .{e, n}); -} -#+end_src -- [[https://blog.orhun.dev/zig-bits-01/][Zig Bits 0x1: Returning slices from functions]] - 这篇文章演示了从一个函数内返回局部变量的问题与解法 -- [[https://blog.deckc.hair/2023-02-22-smoking-hot-binary-search-in-zig.html][Smoking Hot Binary Search In Zig]] -* 项目/工具 -- [[https://tigerbeetle.com/blog/2023-02-21-writing-high-performance-clients-for-tigerbeetle/][Writing high-performance clients for TigerBeetle]] -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-02-01..2023-03-01][Zig 语言更新]] diff --git a/content/monthly/202302.smd b/content/monthly/202302.smd new file mode 100644 index 0000000..53dd636 --- /dev/null +++ b/content/monthly/202302.smd @@ -0,0 +1,72 @@ +--- +.title = "202302 | 精益求精的包管理", +.date = @date("2023-02-26T17:36:12+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2023-01-31T20:05:19+0800" }, +--- + +# [包管理器进展]($section.id('包管理器进展')) +包管理器自 [#14265](https://github.com/ziglang/zig/pull/14265) 合并后一直在不断推进,以下两个是最主要的改变: +- [build system terminology update: package, project, module, dependency](https://github.com/ziglang/zig/issues/14307) + + 这里重新梳理了现在的术语,主要有以下几个: + - `package` 文件的集合,由文件的 hash 值唯一指定,一个 package 可能包含任意数目的 compilation artifacts 与 modules。 + - `dependency` 不同 package 之间的有向边,一个 package 可以有任意个依赖,一个 package 也可以用作任意项目的依赖 + - `module` 文件的集合,每一个模块都有一个 root 文件,在被 `@import` 时用到。 + - `compilation artifact` 编译构建产物,可以是 static library,dynamic library,an executable 或 an object file,对应之前版本的 `LibExeObjStep` +- [introduce Zig Object Notation and use it for the build manifest file (build.zig.zon)](https://github.com/ziglang/zig/pull/14523) + + 使用 zon 格式替代之前的 ini,格式如下: +```zig +.{ + .name = "awesome-cli", + .version = "0.1.0", + .dependencies = .{ + .simargs = .{ + .url = "https://github.com/jiacai2050/simargs/archive/0a1a2afd072cc915009a063075743192fc6b1fd5.tar.gz", + .hash = "1220a6554eccb2e9a9d7d63047e062314851ffd11315b9e6d1b5e06a9dde3275f150", + }, + }, +} +``` + 一些使用了包管理的实际项目: + - [andrewrk/ffmpeg: ffmpeg with the build system replaced by zig](https://github.com/andrewrk/ffmpeg) + - [jiacai2050/loc: Lines of code in Zig](https://github.com/jiacai2050/loc),适配包管理的相关 [commit 修改](https://github.com/jiacai2050/loc/commit/7b01c09a4ba9d3ddc3d067cc6af654601a99035a) + - [PCRE2Project/pcre2: zig build support](https://github.com/PCRE2Project/pcre2/pull/206) + - [nikneym/ws: WebSocket library for Zig](https://github.com/nikneym/ws) + - [Zig package manager · Issue #6 · natecraddock/ziglua](https://github.com/natecraddock/ziglua/issues/6) + +也欢迎大家给自己熟悉的 C/C++ 项目提 PR 让其支持 zig build,让构建不再那么痛苦。 + +# [观点/教程]($section.id('观点/教程')) +- [How a Zig IDE Could Work](https://matklad.github.io/2023/02/10/how-a-zig-ide-could-work.html) +- [Zig tips: v0.11 std.build API / package manager changes | Hexops' devlog](https://devlog.hexops.com/2023/zig-0-11-breaking-build-changes/) +- [pcre2 support zig build](https://lobste.rs/s/zh3ulk/pcre2_support_zig_build) +- [Multi-Object For Loops + Struct-Of-Arrays](https://zig.news/andrewrk/multi-object-for-loops-data-oriented-design-41ob) +- [Zig's Curious Multi-Sequence For Loops](https://kristoff.it/blog/zig-multi-sequence-for-loops),[Lobster 评论](https://lobste.rs/s/ihf30a/zig_s_curious_multi_sequence_for_loops) + 上面这两篇的文章都是演示了最新的 for 语法,开始支持了 range: +```zig +for (0..4) |n| { + std.debug.print("{} ", .{n}); +} +``` + 同时也支持了一次性迭代多个数组的功能: +```zig +var elems = [_][]const u8 { "water", "earth", "fire", "wind" }; +var nats = [_][]const u8 { "tribes", "kingdom", "nation", "nomads" }; + +for (elems, nats) |e, n| { + std.debug.print("{s} {s}\n", .{e, n}); +} +``` +- [Zig Bits 0x1: Returning slices from functions](https://blog.orhun.dev/zig-bits-01/) + 这篇文章演示了从一个函数内返回局部变量的问题与解法 +- [Smoking Hot Binary Search In Zig](https://blog.deckc.hair/2023-02-22-smoking-hot-binary-search-in-zig.html) + +# [项目/工具]($section.id('项目/工具')) +- [Writing high-performance clients for TigerBeetle](https://tigerbeetle.com/blog/2023-02-21-writing-high-performance-clients-for-tigerbeetle/) + +# [Zig 语言更新]($section.id('Zig 语言更新')) +- [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2023-02-01..2023-03-01) diff --git a/content/monthly/202303.org b/content/monthly/202303.org deleted file mode 100644 index c546fae..0000000 --- a/content/monthly/202303.org +++ /dev/null @@ -1,93 +0,0 @@ -#+TITLE: 202303 | 并发编译 -#+DATE: 2023-04-10T19:25:59+0800 -#+LASTMOD: 2023-04-10T19:25:59+0800 - -* 观点/教程 -- [[https://www.reddit.com/r/zig/comments/11wmoky][Creating arbitrary error values by using error.Something syntax]] :: 下面两种方式是等价的: - #+begin_src zig - const err = error.FileNotFound; - const err = (error {FileNotFound}).FileNotFound; - #+end_src -- [[https://notes.eatonphil.com/errors-and-zig.html][Errors and Zig]] :: 主要讲述了 Zig 中如何处理错误,如何携带上下文信息 - #+begin_src zig -var x = try thingThatCouldFail(); - -if (thingThatCouldFail()) |good_value| { - x = good_value; - break; -} else |_| { - // do something that should fix it for the next time - tries -= 1; -} - #+end_src -- [[https://blog.orhun.dev/zig-bits-02/][Zig Bits 0x2: Using defer to defeat memory leaks]] :: 主要介绍了如何探测内存泄漏,如何用 =defer= 来规避 -- [[https://zigurust.gitlab.io/blog/naive-map/][Naive parallel map implementation in Rust and Zig]] :: -- [[https://iamkroot.github.io/blog/zig-memleak][The Curious Case of a Memory Leak in a Zig program]] :: -- [[https://zackoverflow.dev/writing/unsafe-rust-vs-zig/][When Zig is safer and faster than Rust]] :: 比较有趣的文章,作者使用 unsafe rust 与 zig 来实现一个 bytecode 解释器,比较重要的一点是具备 mark-sweep 的 GC 功能。 -- [[https://www.infoworld.com/article/3689648/meet-the-zig-programming-language.html][Meet Zig: The modern alternative to C | InfoWorld]] :: 一篇科普 Zig 的文章 -- [[https://flyx.org/cross-packaging/][Cross-Compiling and packaging C, Go and Zig projects with Nix]] :: 文章介绍了如何 - 利用 Nix 进行交叉编译,对于 C 依赖,作者是通过修改 =pkg-config= 的 =*.pc= 来支持的 -- [[https://www.openmymind.net/Zig-Quirks/][Zig Quirks]] :: 介绍 Zig 的一些特点 -- [[https://matklad.github.io/2023/03/26/zig-and-rust.html][Zig And Rust]] :: -* 项目/工具 -- [[https://github.com/macovedj/doink][macovedj/doink]] :: Making WebAssembly Components with Zig -- [[https://github.com/craftlinks/zig_learn_opengl][craftlinks/zig_learn_opengl]] :: : Follow the Learn-OpenGL book using Zig -- [[https://github.com/b0bleet/zvisor][b0bleet/zvisor]] :: Zig-based Hypervisor (WIP) -- [[https://github.com/flouthoc/ztick][flouthoc/ztick]] :: Tiny desktop utility to keep notes -- [[https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer][ringtailsoftware/zig-wasm-audio-framebuffer]] :: Examples of integrating Zig and Wasm for audio and graphics on the web -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-03-01..2023-04-01][Zig 语言更新]] -- [[https://github.com/ziglang/zig/pull/14647][zig build: run steps in parallel #14647]] :: 一个比较大的更新,编译支持了并发。由于改动比较大,该 PR 合并后出现了一些 bug,一个影响比较大的是 test 的构建方式不一样了。 - #+begin_src diff -- test_step.dependOn(&exe_tests.step); -+ test_step.dependOn(&exe_tests.run().step); - #+end_src -之前老方式写的 =test_step= 在新版本不会再执行了。这样对于 Build 系统来说其实是更合理了。 - -现在 =addTest= 和 =addExecutable= 一样,输出都是 =CompileStep= ,它默认不会执行,需要调用 =run()= 拿到 run step 才可以。 - -* Zig 构建系统介绍 -构建系统其实是 Zig 生态中比较重要的一环,和 Rust 不同,Zig 即是一门编程语言,也是一系列工具链,比如编译 Zig 时,没有 zigc 这个二进制文件,用的是 =zig build-exe= 或 =build-lib= 。 - -=build.zig= 的作用就是提供了一套声明式 API 来构造 =build-exe= 的参数。这里面有一个核心概念: =Step= ,它构成了一个有向无环图,用来驱动整个编译过程。 - -每个 Step 做的事情是由 =MakeFn= 定义的,它的签名是: -#+begin_src zig -pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void; -#+end_src - -但一般来说,我们并不需要自己去实现 MakeFn,内置的 Step 已经可以满足大部分需求,比如: -- =CompileStep= 编译二进制、动态链接库、静态链接库 -- =InstallArtifactStep= 把编译生成的文件复制到 =zig-out= 中 -- =ObjCopyStep= 执行 objcopy 命令 -- =OptionsStep= 可以用来定义编译时的一些常量,作为 module 被当前程序使用,比如把当前项目的构建时间、Git 信息写入到代码中。[[https://www.digitalocean.com/community/tutorials/using-ldflags-to-set-version-information-for-go-applications][类似于 Go]] 里面的 ~go build -ldflags="-X 'package_path.variable_name=new_value'"~ -- =RunStep= 执行二进制 - -Step 中有一类比较特殊,称为 TopLevelStep(简称 TLS),它们可以直接通过 =zig build {topLevelStep}= 的方式来执行,Zig 默认有两个 TLS: -- install,安装二进制 -- uninstall,卸载二进制 - -#+begin_src zig -const exe = b.addExecutable(.{ - .name = "awesome", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, -}); -const run_cmd = exe.run(); -if (b.args) |args| { - run_cmd.addArgs(args); -} -const run_step = b.step("run-" , "Run " ++ name); -run_step.dependOn(&run_cmd.step); - -#+end_src -上面这段代码就定义了一个 TLS: =run= ,它依赖 exe 的执行 step,exe 本身又是个编译 step,因此在 =zig build run= 时,会依次执行: -#+begin_src zig -CompileStep --> RunStep --> TLS -#+end_src - -Zig 的编译系统设计的还是挺巧妙的,而且 =build.zig= 是新人接触 Zig 是打交道最多的代码,如果搞不清它的执行过程,一方面心里比较难受,另一实际方面是影响问题排查。 - -如果读者还是对 =build.zig= 有所困惑,可以参考下面这两个文章,虽然有些过时,但是原理是一样的: -- [[https://mitchellh.com/zig/build-internals][Zig Build System Internals – Mitchell Hashimoto]] -- [[https://zig.news/xq/zig-build-explained-part-1-59lf][zig build explained - part 1 - Zig NEWS ⚡]] diff --git a/content/monthly/202303.smd b/content/monthly/202303.smd new file mode 100644 index 0000000..5fea47b --- /dev/null +++ b/content/monthly/202303.smd @@ -0,0 +1,101 @@ +--- +.title = "202303 | 并发编译", +.date = @date("2023-04-10T19:25:59+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2023-04-10T19:25:59+0800" }, +--- + +# [观点/教程]($section.id('观点/教程')) +- [Creating arbitrary error values by using error.Something syntax](https://www.reddit.com/r/zig/comments/11wmoky) :: 下面两种方式是等价的: + ```zig + const err = error.FileNotFound; + const err = (error {FileNotFound}).FileNotFound; + ``` +- [Errors and Zig](https://notes.eatonphil.com/errors-and-zig.html) :: 主要讲述了 Zig 中如何处理错误,如何携带上下文信息 + ```zig +var x = try thingThatCouldFail(); + +if (thingThatCouldFail()) |good_value| { + x = good_value; + break; +} else |_| { + // do something that should fix it for the next time + tries -= 1; +} + ``` +- [Zig Bits 0x2: Using defer to defeat memory leaks](https://blog.orhun.dev/zig-bits-02/) :: 主要介绍了如何探测内存泄漏,如何用 `defer` 来规避 +- [Naive parallel map implementation in Rust and Zig](https://zigurust.gitlab.io/blog/naive-map/) +- [The Curious Case of a Memory Leak in a Zig program](https://iamkroot.github.io/blog/zig-memleak) +- [When Zig is safer and faster than Rust](https://zackoverflow.dev/writing/unsafe-rust-vs-zig/) :: 比较有趣的文章,作者使用 unsafe rust 与 zig 来实现一个 bytecode 解释器,比重要的一点是具备 mark-sweep 的 GC 功能。 +- [Meet Zig: The modern alternative to C | InfoWorld](https://www.infoworld.com/article/3689648/meet-the-zig-programming-language.html) :: 一篇科普 Zig 的文章 +- [Cross-Compiling and packaging C, Go and Zig projects with Nix](https://flyx.org/cross-packaging/) :: 文章介绍了如何 + 利用 Nix 进行交叉编译,对于 C 依赖,作者是通过修改 `pkg-config` 的 `*.pc` 来支持的 +- [Zig Quirks](https://www.openmymind.net/Zig-Quirks/) :: 介绍 Zig 的一些特点 +- [Zig And Rust](https://matklad.github.io/2023/03/26/zig-and-rust.html) + +# [项目/工具]($section.id('项目/工具')) +- [macovedj/doink](https://github.com/macovedj/doink) :: Making WebAssembly Components with Zig +- [craftlinks/zig_learn_opengl](https://github.com/craftlinks/zig_learn_opengl) :: : Follow the Learn-OpenGL book using Zig +- [b0bleet/zvisor](https://github.com/b0bleet/zvisor) :: Zig-based Hypervisor (WIP) +- [flouthoc/ztick](https://github.com/flouthoc/ztick) :: Tiny desktop utility to keep notes +- [ringtailsoftware/zig-wasm-audio-framebuffer](https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer) :: Examples of integrating Zig and Wasm for audio and graphics on the web + +# [Zig 语言更新]($section.id('Zig 语言更新')) +- [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2023-03-01..2023-04-01) +- [zig build: run steps in parallel #14647](https://github.com/ziglang/zig/pull/14647) :: 一个比较大的更新,编译支持了并发。由于改动比较大,该 PR 合并后出现了一些 bug,一个影响比较大的是 test 的构建方式不一样了。 + ```diff +- test_step.dependOn(&exe_tests.step); ++ test_step.dependOn(&exe_tests.run().step); + ``` +之前老方式写的 `test_step` 在新版本不会再执行了。这样对于 Build 系统来说其实是更合理了。 + +现在 `addTest` 和 `addExecutable` 一样,输出都是 `CompileStep` ,它默认不会执行,需要调用 `run()` 拿到 run step 才可以。 + +# [Zig 构建系统介绍]($section.id('Zig 构建系统介绍')) +构建系统其实是 Zig 生态中比较重要的一环,和 Rust 不同,Zig 即是一门编程语言,也是一个系列工具链,比如编译 Zig 时,没有 zigc 这个二进制文件,用的是 `zig build-exe` 或 `build-lib` 。 + +`build.zig` 的作用就是提供了一套声明式 API 来构造 `build-exe` 的参数。这里面有一个核心概念: `Step` ,它构成了一个有向无环图,用来驱动整个编译过程。 + +每个 Step 做的事情是由 `MakeFn` 定义的,它的签名是: +```zig +pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void; +``` + +但一般来说,我们并不需要自己去实现 MakeFn,内置的 Step 已经可以满足大部分需求,比如: +- `CompileStep` 编译二进制、动态链接库、静态链接库 +- `InstallArtifactStep` 把编译生成的文件复制到 `zig-out` 中 +- `ObjCopyStep` 执行 objcopy 命令 +- `OptionsStep` 可以用来定义编译时的一些常量,作为 module 被当前程序使用,比如把当前项目的构建时间、Git 信息写入到代码中。[类似于 Go](https://www.digitalocean.com/community/tutorials/using-ldflags-to-set-version-information-for-go-applications) 里面的 ~go build -ldflags`"-X 'package_path.variable_name`new_value'"~ +- `RunStep` 执行二进制 + +Step 中有一类比较特殊,称为 TopLevelStep(简称 TLS),它们可以直接通过 `zig build {topLevelStep}` 的方式来执行,Zig 默认有两个 TLS: +- install,安装二进制 +- uninstall,卸载二进制 + +```zig +const exe = b.addExecutable(.{ + .name = "awesome", + .root_source_file ` .{ .path ` "src/main.zig" }, + .target = target, + .optimize = optimize, +}); +const run_cmd = exe.run(); +if (b.args) |args| { + run_cmd.addArgs(args); +} +const run_step = b.step("run-" , "Run " ++ name); +run_step.dependOn(&run_cmd.step); + +``` +上面这段代码就定义了一个 TLS: `run` ,它依赖 exe 的执行 step,exe 本身又是个编译 step,因此在 `zig build run` 时,会依次执行: +```zig +CompileStep --> RunStep --> TLS +``` + +Zig 的编译系统设计的还是挺巧妙的,而且 `build.zig` 是新人接触 Zig 是打交道最多的代码,如果搞不清它的执行过程,一方面心里比较难受,另一实际方面是影响问题排查。 + +如果读者还是对 `build.zig` 有所困惑,可以参考下面这两个文章,虽然有些过时,但是原理是一样的: +- [Zig Build System Internals – Mitchell Hashimoto](https://mitchellh.com/zig/build-internals) +- [zig build explained - part 1 - Zig NEWS ⚡](https://zig.news/xq/zig-build-explained-part-1-59lf) diff --git a/content/monthly/202304.org b/content/monthly/202304.org deleted file mode 100644 index 09776ae..0000000 --- a/content/monthly/202304.org +++ /dev/null @@ -1,37 +0,0 @@ -#+TITLE: 202304 | 首次闯入 Tiobe 前 50 -#+DATE: 2023-05-03T10:31:04+0800 -#+LASTMOD: 2023-09-03T20:10:11+0800 - -* 重大事件 -在 2023 四月份的 [[https://www.tiobe.com/tiobe-index/][Tiobe]] 指数上,Zig [[https://www.techrepublic.com/article/tiobe-index-language-rankings/][排名 46]],尽管 Loris 发推表示这个数字对 Zig 来说没什么实际意义,但对于多数吃瓜群众来说,这还是十分让人鼓舞的。 - -#+begin_quote -For people who heard about Zig just recently: - -- Zig is not 2x faster than Rust, despite what recent benchmarks might lead you to believe. - -- You won't find many Zig jobs for a few years still, despite the Tiobe stuff. - -- Don't join to the Zig community just to rant about Rust. - -— Loris Cro ⚡ (@croloris) [[https://twitter.com/croloris/status/1646555550358831131][April 13, 2023]] -#+end_quote> -* 观点/教程 -- [[https://zig.news/kristoff/when-should-i-use-an-untagged-union-56ek][When should I use an UNTAGGED Union?]] :: Loris 的文章,作者利用访问 untagged union 的未赋值字段是一种 safety-checked UB 的行为,来解决数组成员被重新赋值过的情况。 -- [[https://zig.news/rutenkolk/data-driven-polymorphism-45bk][Data driven polymorphism]] :: 作者用 Zig 来实现 Clojure 语言中的 [[https://clojuredocs.org/clojure.core/defmulti][defmulti]],以达到『动态派发』的效果 -- [[https://zig.news/aryaelfren/testing-and-files-as-structs-n94][Testing and Files as Structs]] :: 作者演示了一个文件作为 struct 的效果,这样导入时就可以用 =const Node = @import("Node.zig")= 的方式了。 -- [[https://zig.news/ityonemo/sneaky-error-payloads-1aka][Sneaky Error Payloads]] :: 一种在错误中携带上下文信息的方式,上一期的月报也有类似讨论。 [[https://notes.eatonphil.com/errors-and-zig.html][Errors and Zig]] -- [[https://www.openmymind.net/Regular-Expressions-in-Zig/][Regular Expressions in Zig]] :: 由于 Zig 现在不支持 C 中的 bitfields,因此无法直接使用 Posix 的 =regex.h= ,这篇文章介绍了一种解决方法。 -- [[https://en.liujiacai.net/2023/04/13/zig-build-system/][Zig Build System]] :: 对 Zig build 系统的介绍 -- [[https://matklad.github.io/2023/04/13/reasonable-bootstrap.html][Reasonable Bootstrap]] :: 探讨了编译器如何实现自举的方式 -- [[https://matklad.github.io/2023/04/23/data-oriented-parallel-value-interner.html][Data Oriented Parallel Value Interner]] :: Matklad 探讨了如何实现一个高性能的 Interner -- [[https://www.youtube.com/watch?v=w3WYdYyjek4][TigerStyle! (Or How To Design Safer Systems in Less Time)]] :: Systems Distributed 23 视频。[[https://www.bilibili.com/video/BV1fm4y1C7XL][B 站链接]] -- [[https://www.youtube.com/watch?v=MqbVoSs0lXk][What Is a Database?]] :: Systems Distributed 23 视频,[[https://www.bilibili.com/video/BV1gP41117zY/][B 站链接]],作者博客:[[https://www.scattered-thoughts.net/][Scattered Thoughts]] -* 项目/工具 -- [[https://zig.news/nameless/coming-soon-to-a-zig-near-you-http-client-5b81][Coming Soon to a Zig Near You: HTTP Client]] :: 对标准库 =std.http= 的介绍。 -- [[https://blog.orhun.dev/zig-bits-03/][Zig Bits 0x3: Mastering project management in Zig]] :: 介绍了如何更好地维护一个 Zig 项目,包括:新增依赖、增加测试覆盖率、增加文档、基于 GitHub Action 做持续集成等。 -- [[https://github.com/ityonemo/zigler][ityonemo/zigler]] :: zig nifs in elixir -- [[https://bingcicle.github.io/posts/ziggifying-kilo.html][Ziggifying Kilo]] :: 使用 Zig 重写 [[https://github.com/antirez/kilo][kilo]] 编辑器,目前仅能在 Linux 上运行 -- [[https://github.com/jakubgiesler/VecZig][jakubgiesler/VecZig]] :: Vector implementation in Zig -- [[https://github.com/b0bleet/zvisor][b0bleet/zvisor]] :: Zvisor is an open-source hypervisor written in the Zig programming language, which provides a modern and efficient approach to systems programming. -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01][Zig 语言更新]] diff --git a/content/monthly/202304.smd b/content/monthly/202304.smd new file mode 100644 index 0000000..2b9cc96 --- /dev/null +++ b/content/monthly/202304.smd @@ -0,0 +1,44 @@ +--- +.title = "202304 | 首次闯入 Tiobe 前 50", +.date = @date("2023-05-03T10:31:04+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2023-09-03T20:10:11+0800" }, +--- + +# [重大事件]($section.id('重大事件')) +在 2023 四月份的 [Tiobe](https://www.tiobe.com/tiobe-index/) 指数上,Zig [排名 46](https://www.techrepublic.com/article/tiobe-index-language-rankings/),尽管 Loris 发推表示这个数字对 Zig 来说没什么实际意义,但对于多数吃瓜群众来说,这还是十分让人鼓舞的。 + +> For people who heard about Zig just recently: +> +> - Zig is not 2x faster than Rust, despite what recent benchmarks might lead you to believe. +> +> - You won't find many Zig jobs for a few years still, despite the Tiobe stuff. +> +> - Don't join to the Zig community just to rant about Rust. +> +> — Loris Cro ⚡ (@croloris) [April 13, 2023](https://twitter.com/croloris/status/1646555550358831131) + +# [观点/教程]($section.id('观点/教程')) +- [When should I use an UNTAGGED Union?](https://zig.news/kristoff/when-should-i-use-an-untagged-union-56ek) :: Loris 的文章,作者利用访问 untagged union 的未赋值字段是一种 safety-checked UB 的行为,来解决数组成员被重新赋值过的情况。 +- [Data driven polymorphism](https://zig.news/rutenkolk/data-driven-polymorphism-45bk) :: 作者用 Zig 来实现 Clojure 语言中的 [defmulti](https://clojuredocs.org/clojure.core/defmulti),以达到『动态派发』的效果 +- [Testing and Files as Structs](https://zig.news/aryaelfren/testing-and-files-as-structs-n94) :: 作者演示了一个文件作为 struct 的效果,这样导入时就可以用 `const Node = @import("Node.zig")` 的方式了。 +- [Sneaky Error Payloads](https://zig.news/ityonemo/sneaky-error-payloads-1aka) :: 一种在错误中携带上下文信息的方式,上一期的月报也有类似讨论。 [Errors and Zig](https://notes.eatonphil.com/errors-and-zig.html) +- [Regular Expressions in Zig](https://www.openmymind.net/Regular-Expressions-in-Zig/) :: 由于 Zig 现在不支持 C 中的 bitfields,因此无法直接使用 Posix 的 `regex.h` ,这篇文章介绍了一种解决方法。 +- [Zig Build System](https://en.liujiacai.net/2023/04/13/zig-build-system/) :: 对 Zig build 系统的介绍 +- [Reasonable Bootstrap](https://matklad.github.io/2023/04/13/reasonable-bootstrap.html) :: 探讨了编译器如何实现自举的方式 +- [Data Oriented Parallel Value Interner](https://matklad.github.io/2023/04/23/data-oriented-parallel-value-interner.html) :: Matklad 探讨了如何实现一个高性能的 Interner +- [TigerStyle! (Or How To Design Safer Systems in Less Time)](https://www.youtube.com/watch?v=w3WYdYyjek4) :: Systems Distributed 23 视频。[B 站链接](https://www.bilibili.com/video/BV1fm4y1C7XL) +- [What Is a Database?](https://www.youtube.com/watch?v=MqbVoSs0lXk) :: Systems Distributed 23 视频,[B 站链接](https://www.bilibili.com/video/BV1gP41117zY/),作者博客:[Scattered Thoughts](https://www.scattered-thoughts.net/) + +# [项目/工具]($section.id('项目/工具')) +- [Coming Soon to a Zig Near You: HTTP Client](https://zig.news/nameless/coming-soon-to-a-zig-near-you-http-client-5b81) :: 对标准库 `std.http` 的介绍。 +- [Zig Bits 0x3: Mastering project management in Zig](https://blog.orhun.dev/zig-bits-03/) :: 介绍了如何更好地维护一个 Zig 项目,包括:新增依赖、增加测试覆盖率、增加文档、基于 GitHub Action 做持续集成等。 +- [ityonemo/zigler](https://github.com/ityonemo/zigler) :: zig nifs in elixir +- [Ziggifying Kilo](https://bingcicle.github.io/posts/ziggifying-kilo.html) :: 使用 Zig 重写 [kilo](https://github.com/antirez/kilo) 编辑器,目前仅能在 Linux 上运行 +- [jakubgiesler/VecZig](https://github.com/jakubgiesler/VecZig) :: Vector implementation in Zig +- [b0bleet/zvisor](https://github.com/b0bleet/zvisor) :: Zvisor is an open-source hypervisor written in the Zig programming language, which provides a modern and efficient approach to systems programming. + +# [Zig 语言更新]($section.id('Zig 语言更新')) +- [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) diff --git a/content/monthly/202305.org b/content/monthly/202305.org deleted file mode 100644 index 1685212..0000000 --- a/content/monthly/202305.org +++ /dev/null @@ -1,33 +0,0 @@ -#+TITLE: 202305 | HTTP is built-in -#+DATE: 2023-06-16T16:32:29+0800 -#+LASTMOD: 2023-06-17T13:32:13+0800 -* 重大事件 -这个月主要的事情就是 HTTP server 在标准库的增加了,具体可参考: -- [[https://zig.news/nameless/coming-soon-to-a-zig-near-you-http-client-5b81][Coming Soon to a Zig Near You: HTTP Client]] -- [[https://github.com/ziglang/zig/issues/910][http server in the standard library · Issue #910]] -* 观点/教程 -- [[https://mitchellh.com/writing/zig-and-swiftui][Integrating Zig and SwiftUI]] :: Mitchell 在用 Zig 实现了一个终端后,虽然没有把源码放出来,但是有了这个文章总结。 -- [[https://matklad.github.io/2023/05/06/zig-language-server-and-cancellation.html][Zig Language Server And Cancellation]] :: Matklad 对 ZLS 实现分析:如何快速响应用户的编辑命令,在作者来看,最主要是 server 端要能够及时取消已经过期的操作。 -- [[https://www.uber.com/en-IT/blog/bootstrapping-ubers-infrastructure-on-arm64-with-zig/][Bootstrapping Uber’s Infrastructure on arm64 with Zig]] :: Zig 社区的老朋友 Motiejus Jakštys 在 Uber 的 arm 化过程中,探索了如何使用 Zig 提供对 Go 代码的交叉编译。 -- [[https://matklad.github.io/2023/06/02/the-worst-zig-version-manager.html][The Worst Zig Version Manager]] :: 在没有 Zig 环境的机器上,如何安装 Zig ?作者的思路是每个项目都附带一个脚本来做这件事,这种方式看似比较笨拙,但很有实用性。 -- [[https://juliette.page/blog/init.html][Writing an init system in a language I don't know]] :: 作者介绍了使用 Zig 开发一个 init 系统的经历,文中主要吐槽了现在 Zig 的文档严重不足 🙃,但看得出,作者依旧是喜欢 Zig 的。 -- [[https://www.openmymind.net/SIMD-With-Zig/][SIMD with Zig]] :: 作者演示了如何利用 SIMD 函数来改进 =indexOf= 函数 -- [[https://zig.news/perky/anytype-antics-2398][Anytype Antics]] :: 作者介绍了如何使用 =anytype= ,一些示例包括:Duck Type、Traits、Comptime Tagged Unions -- [[https://www.youtube.com/watch?v=VU1h-h9doS8][Using Zig | My Initial Thoughts on Ziglang]] :: 视频 -- [[https://www.youtube.com/watch?v=kRrxbRLWsBo&feature=youtu.be][Zig: First Impressions]] :: 视频 -- [[https://lupyuen.codeberg.page/articles/lvgl3.html][(Possibly) LVGL in WebAssembly with Zig Compiler]] :: -- [[https://www.priver.dev/blog/zig/initial-commit-build-system/][Initial Commit: Zig Build System]] :: -- [[https://e-aakash.github.io/update/2023/06/04/resolv-dns-resolver-in-zig.html][Writing DNS resolver in Zig]] :: -* 项目/工具 -- [[https://zigbyexample.github.io/][Zig by Example]] :: 非常好的学习资料。Learn How to use Zig’s Standard Library, by small examples. -- [[https://github.com/sleibrock/zigtoys][sleibrock/zigtoys]] :: All about Zig + WASM and seeing what we can do -- [[https://github.com/Aandreba/zigrc][Aandreba/zigrc]] :: Zig reference-counted pointers inspired by Rust's Rc and Arc -- [[https://github.com/Hanaasagi/struct-env][Hanaasagi/struct]] :: Deserialize env vars into typesafe structs -- [[https://github.com/tristanisham/minigrep-zig][tristanisham/minigrep]] :: A Zig version of the Rust book's minigrep tutorial program -- [[https://github.com/jsomedon/night.zig][jsomedon/night.zig]] :: Simple tool that just install & update zig nightly. -- [[https://github.com/4imothy/termy48][4imothy/termy48]] :: A 2048 game to run in terminal -- [[https://github.com/bnl1/zig-html-example/blob/main/html.zig][zig-html-example]] :: 一个有趣的演示,利用 comptime 来定义 HTML 中的 Tag -- [[https://github.com/KilianVounckx/rayz][KilianVounckx/rayz]] :: 另一个 Raylib 的 bindings -- [[https://github.com/mitchellh/zig-objc][mitchellh/zig]] :: Objective-C runtime bindings for Zig (Zig calling ObjC). - -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01][Zig 语言更新]] diff --git a/content/monthly/202305.smd b/content/monthly/202305.smd new file mode 100644 index 0000000..179215b --- /dev/null +++ b/content/monthly/202305.smd @@ -0,0 +1,40 @@ +--- +.title = "202305 | HTTP is built-in", +.date = @date("2023-06-16T16:32:29+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2023-06-17T13:32:13+0800" }, +--- + +# [重大事件]($section.id('重大事件')) +这个月主要的事情就是 HTTP server 在标准库的增加了,具体可参考: +- [Coming Soon to a Zig Near You: HTTP Client](https://zig.news/nameless/coming-soon-to-a-zig-near-you-http-client-5b81) +- [http server in the standard library · Issue #910](https://github.com/ziglang/zig/issues/910) +# [观点/教程]($section.id('观点/教程')) +- [Integrating Zig and SwiftUI](https://mitchellh.com/writing/zig-and-swiftui) :: Mitchell 在用 Zig 实现了一个终端后,虽然没有把源码放出来,但是有了这个文章总结。 +- [Zig Language Server And Cancellation](https://matklad.github.io/2023/05/06/zig-language-server-and-cancellation.html) :: Matklad 对 ZLS 实现分析:如何快速响应用户的编辑命令,在作者来看,最主要是 server 端要能够及时取消已经过期的操作。 +- [Bootstrapping Uber's Infrastructure on arm64 with Zig](https://www.uber.com/en-IT/blog/bootstrapping-ubers-infrastructure-on-arm64-with-zig/) :: Zig 社区的老朋友 Motiejus Jakštys 在 Uber 的 arm 化过程中,探索了如何使用 Zig 提供对 Go 代码的交叉编译。 +- [The Worst Zig Version Manager](https://matklad.github.io/2023/06/02/the-worst-zig-version-manager.html) :: 在没有 Zig 环境的机器上,如何安装 Zig ?作者的思路是每个项目都附带一个脚本来做这件事,这种方式看似比较笨拙,但很有实用性。 +- [Writing an init system in a language I don't know](https://juliette.page/blog/init.html) :: 作者介绍了使用 Zig 开发一个 init 系统的经历,文中主要吐槽了现在 Zig 的文档严重不足 🙃,但看得出,作者依旧是喜欢 Zig 的。 +- [SIMD with Zig](https://www.openmymind.net/SIMD-With-Zig/) :: 作者演示了如何利用 SIMD 函数来改进 `indexOf` 函数 +- [Anytype Antics](https://zig.news/perky/anytype-antics-2398) :: 作者介绍了如何使用 `anytype` ,一些示例包括:Duck Type、Traits、Comptime Tagged Unions +- [Using Zig | My Initial Thoughts on Ziglang](https://www.youtube.com/watch?v=VU1h-h9doS8) :: 视频 +- [Zig: First Impressions](https://www.youtube.com/watch?v`kRrxbRLWsBo&feature`youtu.be) :: 视频 +- [(Possibly) LVGL in WebAssembly with Zig Compiler](https://lupyuen.codeberg.page/articles/lvgl3.html) :: +- [Initial Commit: Zig Build System](https://www.priver.dev/blog/zig/initial-commit-build-system/) :: +- [Writing DNS resolver in Zig](https://e-aakash.github.io/update/2023/06/04/resolv-dns-resolver-in-zig.html) :: +# [项目/工具]($section.id('项目/工具')) +- [Zig by Example](https://zigbyexample.github.io/) :: 非常好的学习资料。Learn How to use Zig's Standard Library, by small examples. +- [sleibrock/zigtoys](https://github.com/sleibrock/zigtoys) :: All about Zig + WASM and seeing what we can do +- [Aandreba/zigrc](https://github.com/Aandreba/zigrc) :: Zig reference-counted pointers inspired by Rust's Rc and Arc +- [Hanaasagi/struct](https://github.com/Hanaasagi/struct-env) :: Deserialize env vars into typesafe structs +- [tristanisham/minigrep](https://github.com/tristanisham/minigrep-zig) :: A Zig version of the Rust book's minigrep tutorial program +- [jsomedon/night.zig](https://github.com/jsomedon/night.zig) :: Simple tool that just install & update zig nightly. +- [4imothy/termy48](https://github.com/4imothy/termy48) :: A 2048 game to run in terminal +- [zig-html-example](https://github.com/bnl1/zig-html-example/blob/main/html.zig) :: 一个有趣的演示,利用 comptime 来定义 HTML 中的 Tag +- [KilianVounckx/rayz](https://github.com/KilianVounckx/rayz) :: 另一个 Raylib 的 bindings +- [mitchellh/zig](https://github.com/mitchellh/zig-objc) :: Objective-C runtime bindings for Zig (Zig calling ObjC). + +# [Zig 语言更新]($section.id('zig-update')) +[2023-04-01..2023-05-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) diff --git a/content/monthly/202306.org b/content/monthly/202306.org deleted file mode 100644 index bc1f339..0000000 --- a/content/monthly/202306.org +++ /dev/null @@ -1,85 +0,0 @@ -#+TITLE: 202306 | Zig 要分叉了? -#+DATE: 2023-07-01T17:35:43+0800 -#+LASTMOD: 2023-07-01T22:05:35+0800 -* 重大事件 -一个是这个:[[https://ziggit.dev/t/the-zig-subreddit-has-closed/679][The Zig subreddit has closed]],现在 Ziggit 算是官方钦定的论坛了。 - -另一个是月底出来的大新闻:[[https://github.com/ziglang/zig/issues/16270][File for Divorce from LLVM · Issue #16270 · ziglang/zig]] - -这个 issue 主要讨论的是把 LLVM 从 Zig 中彻底移除,动机和优势都列在里面了,这里不再赘述,这里重点说下影响: -1. 去掉 C++/Objc 的支持, -2. 支持的 target 会变少 - -从 issue 本身和 [[https://lobste.rs/s/svhzj9/divorce_from_llvm][Lobsters]]、[[https://news.ycombinator.com/item?id=36529456][HN]] 上的评论看,大家主要担忧的是对 C++ 的支持。由于非常多的基础软件都是构建在 C++ 之上的,如果没有了对 C++ 的支持,那么 Zig 作为工具链这一选择的可行性就大打折扣了,要知道 Zig 之前最主要的卖点就是这个,比如:[[https://kristoff.it/blog/maintain-it-with-zig/][Maintain it With Zig]]。 - -有人提议用 Zig 重写一个 C++ 前端不就好了?但这属于理论上可行,实际没有可操作性的,因为 C++ [[https://news.ycombinator.com/item?id=36532170][太复杂]]了。 - -其实 Zig 从 [[https://ziglang.org/download/0.10.0/release-notes.html][0.10.0]] 版本开始,就一直在着手 [[https://ziglang.org/download/0.10.0/release-notes.html#Self-Hosted-Compiler][Self-Hosted Compiler]] 的开发。看得出,Zig 团队一直在追求极致,从编译速度,到二进制大小(以下数字均来自 0.10.0 的 release note): -- Wall Clock Time: 43 seconds to 40 seconds (7% faster) -- Peak RSS: 9.6 GiB to 2.8 GiB (3.5x less memory used) -- As a point of comparison, a stripped release build of Zig with LLVM is 169 MiB, while without LLVM (but with all the code generation backends you see here) it is 4.4 MiB. - -这个 issue 在互联网上迅速引起了热烈讨论,当然少不了吃瓜群众,以至于 Andrewk 又追加了[[https://github.com/ziglang/zig/issues/16270#issuecomment-1615388680][一条评论]]: -#+begin_quote -I see a lot of speculation in this GitHub Issue from folks who are not involved in Zig in any way. I would respectfully ask you to please take such speculation elsewhere. This issue tracker is for focused technical discussion by those who are actually using Zig, today. The noise in this thread distracts from the valuable comments by users who are sharing their use cases for the relevant features of Zig. - -..which, by the way, I'm one of. For example, my music player reboot branch depends on chromaprint which is, dun dun dun, C++ code. - -I'm not going to simultaneously shoot myself and valuable community members in the face by yanking a load-bearing feature out from underneath us, without any kind of upgrade path. It's a bit unfortunate that the Internet has taken that narrative and run with it. - -For example, one thing to explore, later - once all those boxes above are checked - is whether we can satisfy the C++ compilation use case, as well as the LLVM optimization use case, with the package manager. The results of this exploration will heavily impact the ultimate decision of whether to accept or reject this proposal. - -Please, relax. Nothing is going to happen overnight, and nothing is more important than making sure our esteemed Zig users' needs are taken care of, one way or another. Whatever happens will happen in due time, with due respect for real world projects. This proposal is aspirational - something to look forward to and consider in the coming years. -#+end_quote - -微信群里不少小伙伴也在担忧这个 issue 会不会导致 Zig 的 fork,甚至灭亡。其实看了上面的评论大家就应该放心了,Zig 团队知道用户的需要,不会搬起石头砸自己的脚。 - -我觉得通过这个事件更能坚定我投资 Zig 的信心了,一个追求极致的团队,不需要我们吃瓜群众瞎操心,有这个时间不如去看看 Zig 的各种 backend 进展,能不能给 fix 几个 regression?! - -最后,即便 Zig 这个项目夭折了,我相信通过这个学习的过程也有助于提高我们对系统编程、编译器的理解,不是吗? -* 观点/教程 -- [[https://kristoff.it/blog/note-about-zig-books/][A Note About Zig Books for the Zig Community]] :: Loris Cro 的博客,由于现在 Zig 的关注度越来越高,一些出版社开始联系社区的人出一本 Zig 的书,Loris 这里阐述了与出版社合作的利弊,以及他也在写一本关于 Zig 的书 =Intro to Zig / systems programming= ,由于 Zig 还是不停的开发中,因为书中会尽量少的去涉及 stdlib 的内容。 -- [[https://ziggit.dev/t/how-far-away-is-0-11-really/744][How far away is 0.11 really?]] :: 社区用户对 0.11 版本发布时间的疑问? -- [[https://devlog.hexops.com/2023/mach-ecosystem-c-libraries/][Mach: providing an ecosystem of C libraries using the Zig package manager]] :: 作者在文章讲述了利用 Zig 来打包 C 依赖的优势。 -- [[https://ryleealanza.org/2023/06/21/The-Seamstress-Event-Loop-in-Zig.html][The Seamstress Event Loop In Zig]] :: -- [[https://notes.eatonphil.com/2023-06-19-metaprogramming-in-zig-and-parsing-css.html][Metaprogramming in Zig and parsing CSS]] :: -- [[https://en.liujiacai.net/2023/06/29/embed-git-commit-in-zig/][Embed git commit in Zig programs]] :: 把 git 的 commit id 嵌入项目中非常有助于问题排查 -- [[https://avestura.dev/blog/problems-of-c-and-how-zig-addresses-them][Problems of C, and how Zig addresses them]] :: 不错的入门资料,主要内容: - - Comptime over Textual Replacement Preprocessing - - Memory Management, and Zig Allocators - - Billion dollar mistake vs Zig Optionals - - Pointer arithmetics vs Zig Slices - - Explicit memory alignment - - Arrays as values - - Error handling - - Everything is an expression - - C has a more complex syntax to deal with -- [[https://richiejp.com/zig-cross-compile-ltp-ltx-linux][Minimal Linux VM cross compiled with Clang and Zig]] :: 一篇有意思的文章,作者的任务是跑 Linux kernel 的 test,为了能够方便、简单的跑不同的平台,作者尝试用 Zig 的交叉编译能力来解决这个大难题 -- [[https://www.openmymind.net/Zig-Danling-Pointers/][Zig dangling pointers and segfaults]] :: -- [[http://ratfactor.com/zig/hard][I think Zig is hard...but worth it]] :: 安利 Zig 的文章,[[https://news.ycombinator.com/item?id=36149462][HN 讨论]] - http://ratfactor.com/zig/zighard_700px.jpg -* 项目/工具 -- [[https://github.com/pondzdev/duckdb-proxy/][pondzdev/duckdb]] :: 一个将 DuckDB 数据库通过 HTTP API 暴露出来的代理,主要是利用了 [[https://duckdb.org/docs/api/c/api.html][DuckDB C API]],示例: - #+begin_src bash -# open the database in readonly (DB must exist in this case) -$ ./duckdb-proxy --readonly db/mydatabase.duckdb - -$ curl http://localhost:8012/api/1/exec \ - -d '{"sql": "select version()"}' -{ - "cols": [ - "version()" - ], - "rows": [ - [ - "v0.8.1" - ] - ] -} - #+end_src -- [[https://github.com/ryleelyman/seamstress][ryleelyman/seamstress]] :: Lua monome + OSC scripting environment -- [[https://programming-language-benchmarks.vercel.app/rust-vs-zig][Rust VS Zig benchmarks]] :: Which programming language or compiler is faster -- [[https://github.com/ziglang/shell-completions][ziglang/shell]] :: Shell completions for the Zig compiler. -- [[https://github.com/menduz/zig-steamworks][menduz/zig]] :: Steamwork bindings for Zig. -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-05-01..2023-06-01][Zig 语言更新]] -- [[https://github.com/ziglang/zig/pull/16207][WASI: Implement experimental threading support by Luukdegram · Pull Request #16207 · ziglang/zig]] diff --git a/content/monthly/202306.smd b/content/monthly/202306.smd new file mode 100644 index 0000000..876cc7b --- /dev/null +++ b/content/monthly/202306.smd @@ -0,0 +1,90 @@ +--- +.title = "202306 | Zig 要分叉了?", +.date = @date("2023-07-01T17:35:43+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2023-07-01T22:05:35+0800" }, +--- + +# [重大事件]($section.id('重大事件')) +一个是这个:[The Zig subreddit has closed](https://ziggit.dev/t/the-zig-subreddit-has-closed/679),现在 Ziggit 算是官方钦定的论坛了。 + +另一个是月底出来的大新闻:[File for Divorce from LLVM · Issue #16270 · ziglang/zig](https://github.com/ziglang/zig/issues/16270) + +这个 issue 主要讨论的是把 LLVM 从 Zig 中彻底移除,动机和优势都列在里面了,这里不再赘述,这里重点说下影响: +1. 去掉 C++/Objc 的支持, +2. 支持的 target 会变少 + +从 issue 本身和 [Lobsters](https://lobste.rs/s/svhzj9/divorce_from_llvm)、[HN](https://news.ycombinator.com/item?id=36529456) 上的评论看,大家主要担忧的是对 C++ 的支持。由于非常多的基础软件都是构建在 C++ 之上的,如果没有了对 C++ 的支持,那么 Zig 作为工具链这一选择的可行性就大打折扣了,要知道 Zig 之前最主要的卖点就是这个,比如:[Maintain it With Zig](https://kristoff.it/blog/maintain-it-with-zig/)。 + +有人提议用 Zig 重写一个 C++ 前端不就好了?但这属于理论上可行,实际没有可操作性的,因为 C++ [太复杂](https://news.ycombinator.com/item?id=36532170)了。 + +其实 Zig 从 [0.10.0](https://ziglang.org/download/0.10.0/release-notes.html) 版本开始,就一直在着手 [Self-Hosted Compiler](https://ziglang.org/download/0.10.0/release-notes.html#Self-Hosted-Compiler) 的开发。看得出,Zig 团队一直在追求极致,从编译速度,到二进制大小(以下数字均来自 0.10.0 的 release note): +- Wall Clock Time: 43 seconds to 40 seconds (7% faster) +- Peak RSS: 9.6 GiB to 2.8 GiB (3.5x less memory used) +- As a point of comparison, a stripped release build of Zig with LLVM is 169 MiB, while without LLVM (but with all the code generation backends you see here) it is 4.4 MiB. + +这个 issue 在互联网上迅速引起了热烈讨论,当然少不了吃瓜群众,以至于 Andrewk 又追加了[一条评论](https://github.com/ziglang/zig/issues/16270#issuecomment-1615388680): +> +I see a lot of speculation in this GitHub Issue from folks who are not involved in Zig in any way. I would respectfully ask you to please take such speculation elsewhere. This issue tracker is for focused technical discussion by those who are actually using Zig, today. The noise in this thread distracts from the valuable comments by users who are sharing their use cases for the relevant features of Zig. + +..which, by the way, I'm one of. For example, my music player reboot branch depends on chromaprint which is, dun dun dun, C++ code. + +I'm not going to simultaneously shoot myself and valuable community members in the face by yanking a load-bearing feature out from underneath us, without any kind of upgrade path. It's a bit unfortunate that the Internet has taken that narrative and run with it. + +For example, one thing to explore, later - once all those boxes above are checked - is whether we can satisfy the C++ compilation use case, as well as the LLVM optimization use case, with the package manager. The results of this exploration will heavily impact the ultimate decision of whether to accept or reject this proposal. + +Please, relax. Nothing is going to happen overnight, and nothing is more important than making sure our esteemed Zig users' needs are taken care of, one way or another. Whatever happens will happen in due time, with due respect for real world projects. This proposal is aspirational - something to look forward to and consider in the coming years. + +微信群里不少小伙伴也在担忧这个 issue 会不会导致 Zig 的 fork,甚至灭亡。其实看了上面的评论大家就应该放心了,Zig 团队知道用户的需要,不会搬起石头砸自己的脚。 + +我觉得通过这个事件更能坚定我投资 Zig 的信心了,一个追求极致的团队,不需要我们吃瓜群众瞎操心,有这个时间不如去看看 Zig 的各种 backend 进展,能不能给 fix 几个 regression?! + +最后,即便 Zig 这个项目夭折了,我相信通过这个学习的过程也有助于提高我们对系统编程、编译器的理解,不是吗? +# [观点/教程]($section.id('观点/教程')) +- [A Note About Zig Books for the Zig Community](https://kristoff.it/blog/note-about-zig-books/) :: Loris Cro 的博客,由于现在 Zig 的关注度越来越高,一些出版社开始联系社区的人出一本 Zig 的书,Loris 这里阐述了与出版社合作的利弊,以及他也在写一本关于 Zig 的书 `Intro to Zig / systems programming` ,由于 Zig 还是不停的开发中,因为书中会尽量少的去涉及 stdlib 的内容。 +- [How far away is 0.11 really?](https://ziggit.dev/t/how-far-away-is-0-11-really/744) :: 社区用户对 0.11 版本发布时间的疑问? +- [Mach: providing an ecosystem of C libraries using the Zig package manager](https://devlog.hexops.com/2023/mach-ecosystem-c-libraries/) :: 作者在文章讲述了利用 Zig 来打包 C 依赖的优势。 +- [The Seamstress Event Loop In Zig](https://ryleealanza.org/2023/06/21/The-Seamstress-Event-Loop-in-Zig.html) :: +- [Metaprogramming in Zig and parsing CSS](https://notes.eatonphil.com/2023-06-19-metaprogramming-in-zig-and-parsing-css.html) :: +- [Embed git commit in Zig programs](https://en.liujiacai.net/2023/06/29/embed-git-commit-in-zig/) :: 把 git 的 commit id 嵌入项目中非常有助于问题排查 +- [Problems of C, and how Zig addresses them](https://avestura.dev/blog/problems-of-c-and-how-zig-addresses-them) :: 不错的入门资料,主要内容: + - Comptime over Textual Replacement Preprocessing + - Memory Management, and Zig Allocators + - Billion dollar mistake vs Zig Optionals + - Pointer arithmetics vs Zig Slices + - Explicit memory alignment + - Arrays as values + - Error handling + - Everything is an expression + - C has a more complex syntax to deal with +- [Minimal Linux VM cross compiled with Clang and Zig](https://richiejp.com/zig-cross-compile-ltp-ltx-linux) :: 一篇有意思的文章,作者的任务是跑 Linux kernel 的 test,为了能够方便、简单的跑不同的平台,作者尝试用 Zig 的交叉编译能力来解决这个大难题 +- [Zig dangling pointers and segfaults](https://www.openmymind.net/Zig-Danling-Pointers/) :: +- [I think Zig is hard...but worth it](http://ratfactor.com/zig/hard) :: 安利 Zig 的文章,[HN 讨论](https://news.ycombinator.com/item?id=36149462) + http://ratfactor.com/zig/zighard_700px.jpg +# [项目/工具]($section.id('项目/工具')) +- [pondzdev/duckdb](https://github.com/pondzdev/duckdb-proxy/) :: 一个将 DuckDB 数据库通过 HTTP API 暴露出来的代理,主要是利用了 [DuckDB C API](https://duckdb.org/docs/api/c/api.html),示例: + ```bash +# open the database in readonly (DB must exist in this case) +$ ./duckdb-proxy --readonly db/mydatabase.duckdb + +$ curl http://localhost:8012/api/1/exec \ + -d '{"sql": "select version()"}' +{ + "cols": [ + "version()" + ], + "rows": [ + [ + "v0.8.1" + ] + ] +} + ``` +- [ryleelyman/seamstress](https://github.com/ryleelyman/seamstress) :: Lua monome + OSC scripting environment +- [Rust VS Zig benchmarks](https://programming-language-benchmarks.vercel.app/rust-vs-zig) :: Which programming language or compiler is faster +- [ziglang/shell](https://github.com/ziglang/shell-completions) :: Shell completions for the Zig compiler. +- [menduz/zig](https://github.com/menduz/zig-steamworks) :: Steamwork bindings for Zig. +# [[Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2023-05-01..2023-06-01)]($section.id('[Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2023-05-01..2023-06-01)')) +- [WASI: Implement experimental threading support by Luukdegram · Pull Request #16207 · ziglang/zig](https://github.com/ziglang/zig/pull/16207) diff --git a/content/monthly/202307.org b/content/monthly/202307.org deleted file mode 100644 index e769b02..0000000 --- a/content/monthly/202307.org +++ /dev/null @@ -1,95 +0,0 @@ -#+TITLE: 202307 | 异步缺席 0.11 -#+DATE: 2023-08-07T20:05:29+0800 -#+LASTMOD: 2023-08-09T08:15:59+0800 -* 重大事件 -Andrewk 在最新的文章 -[[https://ziglang.org/news/0.11.0-postponed-again/][The Upcoming Release Postponed Two More Weeks and Lacks Async Functions]] 中指出,即将发布的 0.11 中将不会包含对异步的支持,现在异步是在 [[https://github.com/ziglang/zig/tree/stage2-async][stage2-async]] 这个分支上来开发,但是在开发过程中,总是有其他事情出现,然后 Andrewk 就先去搞这些事情了。因此,把对异步的支持放到 0.12 上了。 - -另一件事是 [[https://ziglang.org/news/welcome-jacob-young/][Jacob Young Joins the Core Zig Team]],Core Team 迎来了另一位全职开发者,常用 ID [[https://github.com/jacobly0][jacobly0]],下面是他最近的提交记录: -- https://github.com/ziglang/zig/commits?author=jacobly0&since=2023-06-31 - -恭喜 Core Team,又添一虎将! -* 观点/教程 -- [[https://tigerbeetle.com/blog/2023-07-26-copy-hunting/][Copy Hunting | TigerBeetle]] :: 比较有意思的文章,通过分析 LLVM 的 IR,来避免程序中不必要的 memcpy 以及如何减少 binary 体积 -- [[https://double-trouble.dev/post/zbench/][Taking off with Zig: Putting the Z in Benchmark]] :: Zig 入门文章,作者使用 Zig 来实现了一个 benchmark 库,里面有作者的感受,语言小巧,主要的语言特点:独特的错误处理、显式的内存控制、comptime 执行 -- [[https://hackernoon.com/the-new-wave-of-programming-languages-exploring-the-hidden-gems][The New Wave of Programming Languages: Pony, Zig, Crystal, Vlang, and Julia]] :: 多种语言的对比,Zig 部分的对比: - -| Pros | Cons | -|--------------------------------------------+-----------------------------| -| Excellent low-level control over code | Relatively new and evolving | -| Emphasis on safety and reliability | Limited library support | -| Good interoperability with other languages | Steep learning curve | - -- [[https://www.aolium.com/karlseguin/cf03dee6-90e1-85ac-8442-cf9e6c11602a][Parsing timestamps and generating RFC3339 dates in Zig]] :: Zig 标准库里有返回 unixtime 时间戳的函数,但是没有格式化函数,作者这里给出了一种实现: - #+begin_src zig -pub const DateTime = struct { - year: u16, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, -}; - -pub fn fromTimestamp(ts: u64) DateTime { - const SECONDS_PER_DAY = 86400; - const DAYS_PER_YEAR = 365; - const DAYS_IN_4YEARS = 1461; - const DAYS_IN_100YEARS = 36524; - const DAYS_IN_400YEARS = 146097; - const DAYS_BEFORE_EPOCH = 719468; - - const seconds_since_midnight: u64 = @rem(ts, SECONDS_PER_DAY); - var day_n: u64 = DAYS_BEFORE_EPOCH + ts / SECONDS_PER_DAY; - var temp: u64 = 0; - - temp = 4 * (day_n + DAYS_IN_100YEARS + 1) / DAYS_IN_400YEARS - 1; - var year: u16 = @intCast(100 * temp); - day_n -= DAYS_IN_100YEARS * temp + temp / 4; - - temp = 4 * (day_n + DAYS_PER_YEAR + 1) / DAYS_IN_4YEARS - 1; - year += @intCast(temp); - day_n -= DAYS_PER_YEAR * temp + temp / 4; - - var month: u8 = @intCast((5 * day_n + 2) / 153); - const day: u8 = @intCast(day_n - (@as(u64, @intCast(month)) * 153 + 2) / 5 + 1); - - month += 3; - if (month > 12) { - month -= 12; - year += 1; - } - - return DateTime{ - .year = year, - .month = month, - .day = day, - .hour = @intCast(seconds_since_midnight / 3600), - .minute = @intCast(seconds_since_midnight % 3600 / 60), - .second = @intCast(seconds_since_midnight % 60) - }; -} - #+end_src -- [[https://www.aolium.com/karlseguin/46252c5b-587a-c419-be96-a0ccc2f11de4][Custom JSON serialization in Zig]] :: Zig 里面虽然有很好的 JSON 序列化支持,但是有些时候我们需要自定义某个字段的解析,与 Go =json.Marshaller= 类似,Zig 会在序列化时查找类型的 =jsonStringify= 函数,通过实现这个函数就可以达到目的,文中给出了个示例: - #+begin_src zig -const NumericBoolean = struct { - value: bool, - - pub fn jsonStringify(self: NumericBoolean, out: anytype) !void { - const json = if (self.value) "1" else "0"; - return out.print("{s}", .{json}); - } -}; - #+end_src -- [[https://matklad.github.io/2023/07/16/three-different-cuts.html][Three Different Cuts]] :: matklad 的文章,介绍了 Rust、Go、Zig 三种语言的 Cut 实现 -- [[https://blog.orhun.dev/zig-bits-04/][Zig Bits 0x4: Building an HTTP client/server from scratch]] :: 介绍了 std.http 这个模块的使用 -- [[https://tigerbeetle.com/blog/2023-07-11-we-put-a-distributed-database-in-the-browser/][We Put a Distributed Database In the Browser – And Made a Game of It]] :: TigerBeetle 的新花样,把数据库搬到了 Web 上。[[https://news.ycombinator.com/item?id=36680535][HN 讨论]] -- [[https://zig.news/gwenzek/zig-great-design-for-great-optimizations-638][Zig: great design for great optimizations]] :: 比较有意思的文章,作者比较了 Clang、Zig 对相同逻辑代码生产的 LLVM IR,Zig 生成的 IR 更加精简 - -* 项目/工具 -- [[https://blog.turso.tech/zig-helped-us-move-data-to-the-edge-here-are-our-impressions-67d3a9c45af4][Zig helped us move data to the Edge. Here are our impressions]] :: [[https://turso.tech/][Turso]] 公司的官博,它们公司的产品时边缘数据库,自动同步 PG 的表到 Edge 端,减少访问的时延。在这篇文章里他们介绍了使用 Zig 编写 PostgreSQL 插件的经历,得益于 =translate-c= ,他们可以直接从已有的 C 代码开始构建他们的产品。插件地址:[[https://github.com/turso-extended/pg_turso][pg_turso]] -- [[https://github.com/tensorush/meduza][tensorush/meduza]] :: 🦎 🧜‍♀️ Zig codebase graph generator that emits a Mermaid class diagram -- [[https://github.com/AndreaOrru/zen][AndreaOrru/zen]] :: Experimental operating system written in Zig -- [[https://github.com/EugenHotaj/zig_gpt2][EugenHotaj/zig_gpt2]] :: GPT-2 inference engine written in Zig - -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-06-01..2023-07-01][Zig 语言更新]] diff --git a/content/monthly/202307.smd b/content/monthly/202307.smd new file mode 100644 index 0000000..55c06d7 --- /dev/null +++ b/content/monthly/202307.smd @@ -0,0 +1,102 @@ +--- +.title = "202307 | 异步缺席 0.11", +.date = @date("2023-08-07T20:05:29+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2023-08-09T08:15:59+0800" }, +--- + +# [重大事件]($section.id('重大事件')) +Andrewk 在最新的文章 +[The Upcoming Release Postponed Two More Weeks and Lacks Async Functions](https://ziglang.org/news/0.11.0-postponed-again/) 中指出,即将发布的 0.11 中将不会包含对异步的支持,现在异步是在 [stage2-async](https://github.com/ziglang/zig/tree/stage2-async) 这个分支上来开发,但是在开发过程中,总是有其他事情出现,然后 Andrewk 就先去搞这些事情了。因此,把对异步的支持放到 0.12 上了。 + +另一件事是 [Jacob Young Joins the Core Zig Team](https://ziglang.org/news/welcome-jacob-young/),Core Team 迎来了另一位全职开发者,常用 ID [jacobly0](https://github.com/jacobly0),下面是他最近的提交记录: +- https://github.com/ziglang/zig/commits?author`jacobly0&since`2023-06-31 + +恭喜 Core Team,又添一虎将! +# [观点/教程]($section.id('观点/教程')) +- [Copy Hunting | TigerBeetle](https://tigerbeetle.com/blog/2023-07-26-copy-hunting/) :: 比较有意思的文章,通过分析 LLVM 的 IR,来避免程序中不必要的 memcpy 以及如何减少 binary 体积 +- [Taking off with Zig: Putting the Z in Benchmark](https://double-trouble.dev/post/zbench/) :: Zig 入门文章,作者使用 Zig 来实现了一个 benchmark 库,里面有作者的感受,语言小巧,主要的语言特点:独特的错误处理、显式的内存控制、comptime 执行 +- [The New Wave of Programming Languages: Pony, Zig, Crystal, Vlang, and Julia](https://hackernoon.com/the-new-wave-of-programming-languages-exploring-the-hidden-gems) :: 多种语言的对比,Zig 部分的对比: + +| Pros | Cons | +|--------------------------------------------+-----------------------------| +| Excellent low-level control over code | Relatively new and evolving | +| Emphasis on safety and reliability | Limited library support | +| Good interoperability with other languages | Steep learning curve | + +- [Parsing timestamps and generating RFC3339 dates in Zig](https://www.aolium.com/karlseguin/cf03dee6-90e1-85ac-8442-cf9e6c11602a) :: Zig 标准库里有返回 unixtime 时间戳的函数,但是没有格式化函数,作者这里给出了一种实现: + ```zig +pub const DateTime = struct { + year: u16, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, +}; + +pub fn fromTimestamp(ts: u64) DateTime { + const SECONDS_PER_DAY = 86400; + const DAYS_PER_YEAR = 365; + const DAYS_IN_4YEARS = 1461; + const DAYS_IN_100YEARS = 36524; + const DAYS_IN_400YEARS = 146097; + const DAYS_BEFORE_EPOCH = 719468; + + const seconds_since_midnight: u64 = @rem(ts, SECONDS_PER_DAY); + var day_n: u64 = DAYS_BEFORE_EPOCH + ts / SECONDS_PER_DAY; + var temp: u64 = 0; + + temp = 4 * (day_n + DAYS_IN_100YEARS + 1) / DAYS_IN_400YEARS - 1; + var year: u16 = @intCast(100 * temp); + day_n -= DAYS_IN_100YEARS * temp + temp / 4; + + temp = 4 * (day_n + DAYS_PER_YEAR + 1) / DAYS_IN_4YEARS - 1; + year += @intCast(temp); + day_n -= DAYS_PER_YEAR * temp + temp / 4; + + var month: u8 = @intCast((5 * day_n + 2) / 153); + const day: u8 = @intCast(day_n - (@as(u64, @intCast(month)) * 153 + 2) / 5 + 1); + + month += 3; + if (month > 12) { + month -= 12; + year += 1; + } + + return DateTime{ + .year = year, + .month = month, + .day = day, + .hour = @intCast(seconds_since_midnight / 3600), + .minute = @intCast(seconds_since_midnight % 3600 / 60), + .second = @intCast(seconds_since_midnight % 60) + }; +} + ``` +- [Custom JSON serialization in Zig](https://www.aolium.com/karlseguin/46252c5b-587a-c419-be96-a0ccc2f11de4) :: Zig 里面虽然有很好的 JSON 序列化支持,但是有些时候我们需要自定义某个字段的解析,与 Go `json.Marshaller` 类似,Zig 会在序列化时查找类型的 `jsonStringify` 函数,通过实现这个函数就可以达到目的,文中给出了个示例: + ```zig +const NumericBoolean = struct { + value: bool, + + pub fn jsonStringify(self: NumericBoolean, out: anytype) !void { + const json = if (self.value) "1" else "0"; + return out.print("{s}", .{json}); + } +}; + ``` +- [Three Different Cuts](https://matklad.github.io/2023/07/16/three-different-cuts.html) :: matklad 的文章,介绍了 Rust、Go、Zig 三种语言的 Cut 实现 +- [Zig Bits 0x4: Building an HTTP client/server from scratch](https://blog.orhun.dev/zig-bits-04/) :: 介绍了 std.http 这个模块的使用 +- [We Put a Distributed Database In the Browser – And Made a Game of It](https://tigerbeetle.com/blog/2023-07-11-we-put-a-distributed-database-in-the-browser/) :: TigerBeetle 的新花样,把数据库搬到了 Web 上。[HN 讨论](https://news.ycombinator.com/item?id=36680535) +- [Zig: great design for great optimizations](https://zig.news/gwenzek/zig-great-design-for-great-optimizations-638) :: 比较有意思的文章,作者比较了 Clang、Zig 对相同逻辑代码生产的 LLVM IR,Zig 生成的 IR 更加精简 + +# [项目/工具]($section.id('项目/工具')) +- [Zig helped us move data to the Edge. Here are our impressions](https://blog.turso.tech/zig-helped-us-move-data-to-the-edge-here-are-our-impressions-67d3a9c45af4) :: [Turso](https://turso.tech/) 公司的官博,它们公司的产品时边缘数据库,自动同步 PG 的表到 Edge 端,减少访问的时延。在这篇文章里他们介绍了使用 Zig 编写 PostgreSQL 插件的经历,得益于 `translate-c` ,他们可以直接从已有的 C 代码开始构建他们的产品。插件地址:[pg_turso](https://github.com/turso-extended/pg_turso) +- [tensorush/meduza](https://github.com/tensorush/meduza) :: 🦎 🧜‍♀️ Zig codebase graph generator that emits a Mermaid class diagram +- [AndreaOrru/zen](https://github.com/AndreaOrru/zen) :: Experimental operating system written in Zig +- [EugenHotaj/zig_gpt2](https://github.com/EugenHotaj/zig_gpt2) :: GPT-2 inference engine written in Zig + +# [Zig 语言更新]($section.id('zig-update')) +[2023-06-01..2023-07-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-06-01..2023-07-01) diff --git a/content/monthly/202308.org b/content/monthly/202308.org deleted file mode 100644 index 47c662b..0000000 --- a/content/monthly/202308.org +++ /dev/null @@ -1,139 +0,0 @@ -#+TITLE: 202308 | 0.11 正式发布 -#+DATE: 2023-09-03T19:38:04+0800 -#+LASTMOD: 2023-09-04T22:05:21+0800 -* [[https://ziglang.org/download/0.11.0/release-notes.html#x86-Backend][0.11 正式发布]] -0.11 终于在 8 月 4 号释出了,下面来看看它的一些重要改进吧。[[https://news.ycombinator.com/item?id=36995735][HN 讨论]] -** Peer Type Resolution Improvements -对等类型解析算法得到改进,下面是一些在 0.10 中不能解析,但在 0.11 中可以解析的例子: -| Peer Types | Resolved Type | -| [:s]const T, []T | []const T | -| E!*T, ?*T | E!?*T | -| [*c]T, @TypeOf(null) | [*c]T | -| ?u32, u8 | ?u32 | -| [2]u32, struct { u32, u32 } | [2]u32 | -| *const @TypeOf(.{}), []const u8 | []const u8 | - -而且现在使用 =@intCast= 这类 builtin 都只接受一个参数,目前类型根据上下文自动推断出来。 -** Multi-Object For Loops -可以同时对多个对象进行遍历: -#+begin_src zig -// 之前 -for (input) |x, i| { - output[i] = x * 2; -} - -// 现在 -for (input, 0..) |x, i| { - output[i] = x * 2; -} -#+end_src -** @min and @max -主要有两个改动: -1. 这两个 builtin 现在支持任意多个参数 -2. 返回的类型,会尽可能的紧凑 -#+begin_src zig -test "@min/@max refines result type" { - const x: u8 = 20; // comptime-known - var y: u64 = 12345; - // Since an exact bound is comptime-known, the result must fit in a u5 - comptime assert(@TypeOf(@min(x, y)) == u5); - - var x_rt: u8 = x; // runtime-known - // Since one argument to @min is a u8, the result must fit in a u8 - comptime assert(@TypeOf(@min(x_rt, y)) == u8); -} -#+end_src -** @inComptime -新加的 builtin,用于判断执行是否在 comptime 环境下执行: -#+begin_src zig -const global_val = blk: { - assert(@inComptime()); - break :blk 123; -}; - -comptime { - assert(@inComptime()); -} - -fn f() u32 { - if (@inComptime()) { - return 1; - } else { - return 2; - } -} - -test "@inComptime" { - try expectEqual(true, comptime @inComptime()); - try expectEqual(false, @inComptime()); - try expectEqual(@as(u32, 1), comptime f()); - try expectEqual(@as(u32, 2), f()); -} -#+end_src -** 类型转化相关 builtin 的重命名 -之前 =@xToY= 形式的 builtin 现在已经改成了 =@yFromX= ,这样的主要好处是便于阅读(从右向左),这是[[https://github.com/ziglang/zig/issues/6128][草案]]。 -** Tuple 类型声明 -现在可以直接用无 field 名字的 struct 来声明 tuple 类型: -#+begin_src zig -test "tuple declarations" { - const T = struct { u32, []const u8 }; - var t: T = .{ 1, "foo" }; - try expect(t[0] == 1); - try expectEqualStrings(t[1], "foo"); - - var mul = t ** 3; - try expect(@TypeOf(mul) != T); - try expect(mul.len == 6); - try expect(mul[2] == 1); - try expectEqualStrings(mul[3], "foo"); - - var t2: T = .{ 2, "bar" }; - var cat = t ++ t2; - try expect(@TypeOf(cat) != T); - try expect(cat.len == 4); - try expect(cat[2] == 2); - try expectEqualStrings(cat[3], "bar"); -} -#+end_src - -之前只能用 =std.meta.Tuple= 函数来定义: -#+begin_src diff -- const testcases = [_]std.meta.Tuple(&[_]type{ []const u8, []const u8, bool }){ -+ const testcases = [_]struct { []const u8, []const u8, bool }{ -#+end_src -** 排序 -现在排序算法分布两类: -- 稳定,blocksort 算法 -- 不稳定,[[https://github.com/ziglang/zig/pull/15412][pdqsort]] 算法,它结合了随机快速排序的快速平均情况和堆排序的快速最坏情况。 -与堆排的快速最差情况相结合,同时在具有特定模式的输入上达到线性时间。 -** Stack Unwinding -Zig 之前依赖 [[https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers][frame pointer]] 来做堆栈回卷,但它本身有些代价,因此线上环境可能会通过 =-fomit-frame-pointer= 将其禁用掉。 - -为了在这种情况下依然能够获取 panic 是的堆栈信息,Zig 现在支持了通过 DWARF unwind tables 和 MachO compact unwind information 来会恢复堆栈,详见:[[https://github.com/ziglang/zig/pull/15823][#15823]]。 -** 包管理 -0.11 首次正式引入了包管理器,具体解释可以参考:[[https://en.liujiacai.net/2023/04/13/zig-build-system/][Zig Build System]],而且很重要一点,step 之间可以[[https://ziglang.org/download/0.11.0/release-notes.html#Steps-Run-In-Parallel][并发执行]], -** Bootstrapping -C++ 实现的 Zig 编译器已经被彻底移除,这意味着 =-fstage1= 不再生效,Zig 现在只需要一个 2.4M 的 WebAssembly 文件和一个 C 编辑器即可,工作细节可以参考:[[https://ziglang.org/news/goodbye-cpp/][Goodbye to the C++ Implementation of Zig]]。 -** 代码生成 -虽然 Zig 编译器现在还是主要使用 LLVM 来进行代码生成,但在这次发布中,其他几个后端也有了非常大的进步: -1. C 后端,行为测试通过 98%,而且生成的 C 代码兼容微软的 MSVC,用在了 bootstrapping 中 -2. x86 后端,行为测试通过 88% -3. aarch64 后端,刚开始 -4. WebAssembly 后端,行为测试通过 86%, -5. SPIR-V 后端,SPIR-V 是在 GPU 上运行的着色器(shader)和内核的字节码表示法。目前,Zig 的 SPIR-V 后端专注于为 OpenCL 内核生成代码,不过未来可能也会支持兼容 Vulkan 的着色器。 -** 增量编译 -虽然这仍是一个高度 WIP 的功能,但这一版本周期中的许多改进为编译器的增量编译功能铺平了道路。其中最重要的是 [[https://github.com/ziglang/zig/pull/15569][InternPool]]。Zig 用户大多看不到这一改动,但它为编译器带来了许多好处,其中之一就是我们现在更接近增量编译了。增量编译将是 0.12.0 发布周期的重点。 -* 观点/教程 -- [[https://www.aolium.com/karlseguin/4013ac14-2457-479b-e59b-e603c04673c8][Error Handling In Zig]] :: 又一篇讨论错误处理的文章 -- [[https://www.1a-insec.net/blog/10-type-magic-in-zig/][Commiting Type Crimes in Zig]] :: 对 Zig 类型系统的另一种用法,有些和[[https://zh.wikipedia.org/wiki/%E9%82%B1%E5%A5%87%E6%95%B0][邱奇数]]类似。 -- [[https://www.youtube.com/watch?v=kxT8-C1vmd4][Zig in 100 Seconds]] :: Zig 宣传视频 -- [[https://www.youtube.com/watch?v=vKKTMBoxpS8][Zig Build System & How to Build Software From Source • Andrew Kelley • GOTO 2023]] :: Andrew 关于构建系统的视频,[[https://www.bilibili.com/video/BV1Mh4y1K7yc/][B 站链接]]、[[https://www.youtube.com/watch?v=vKKTMBoxpS8][Youbute]] -- [[https://rbino.com/posts/wrap-your-nif-with-zig/][Wrap your NIF with Zig]] :: NIF 是 Elixir 中进行 FFI 调用的方式,如果用原生 C 接口来用,会需要写很多胶水代码, - 作者这里用 comptime 特性来定义了一个 =make_nif_wrapper= 来简化 NIF 的实现,这个技巧在与 C 项目交互时十分有用。 -- [[https://matklad.github.io/2023/08/09/types-and-zig.html][Types and the Zig Programming Language]] :: matklad 对 Zig 类型系统的总结 -- [[https://andrewkelley.me/post/goodbye-twitter-reddit.html][So Long, Twitter and Reddit]] :: Andrew 的最新文章,远离社交平台! -- [[https://zig.news/edyu/wtf-is-zig-comptime-and-inline-257b][WTF is Zig Comptime (and Inline)]] :: -- [[https://double-trouble.dev/post/zbench/][Taking off with Zig: Putting the Z in Benchmark — Double Trouble]] :: -* 项目/工具 -- [[https://devlog.hexops.com/2023/mach-v0.2-released/][Mach v0.2 released]] -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-07-01..2023-08-01][Zig 语言更新]] diff --git a/content/monthly/202308.smd b/content/monthly/202308.smd new file mode 100644 index 0000000..e403d00 --- /dev/null +++ b/content/monthly/202308.smd @@ -0,0 +1,146 @@ +--- +.title = "202308 | 0.11 正式发布", +.date = @date("2023-09-03T19:38:04+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2023-09-04T22:05:21+0800" }, +--- + +# [[0.11 正式发布](https://ziglang.org/download/0.11.0/release-notes.html#x86-Backend)]($section.id('[0.11 正式发布](https://ziglang.org/download/0.11.0/release-notes.html#x86-Backend)')) +0.11 终于在 8 月 4 号释出了,下面来看看它的一些重要改进吧。[HN 讨论](https://news.ycombinator.com/item?id=36995735) +## [Peer Type Resolution Improvements]($section.id('Peer Type Resolution Improvements')) +对等类型解析算法得到改进,下面是一些在 0.10 中不能解析,但在 0.11 中可以解析的例子: +| Peer Types | Resolved Type | +| [:s]const T, []T | []const T | +| E!*T, ?*T | E!?*T | +| [*c]T, @TypeOf(null) | [*c]T | +| ?u32, u8 | ?u32 | +| [2]u32, struct { u32, u32 } | [2]u32 | +| *const @TypeOf(.{}), []const u8 | []const u8 | + +而且现在使用 `@intCast` 这类 builtin 都只接受一个参数,目前类型根据上下文自动推断出来。 +## [Multi-Object For Loops]($section.id('Multi-Object For Loops')) +可以同时对多个对象进行遍历: +```zig +// 之前 +for (input) |x, i| { + output[i] = x * 2; +} + +// 现在 +for (input, 0..) |x, i| { + output[i] = x * 2; +} +``` +## [@min and @max]($section.id('@min and @max')) +主要有两个改动: +1. 这两个 builtin 现在支持任意多个参数 +2. 返回的类型,会尽可能的紧凑 +```zig +test "@min/@max refines result type" { + const x: u8 = 20; // comptime-known + var y: u64 = 12345; + // Since an exact bound is comptime-known, the result must fit in a u5 + comptime assert(@TypeOf(@min(x, y)) == u5); + + var x_rt: u8 = x; // runtime-known + // Since one argument to @min is a u8, the result must fit in a u8 + comptime assert(@TypeOf(@min(x_rt, y)) == u8); +} +``` +## [@inComptime]($section.id('@inComptime')) +新加的 builtin,用于判断执行是否在 comptime 环境下执行: +```zig +const global_val = blk: { + assert(@inComptime()); + break :blk 123; +}; + +comptime { + assert(@inComptime()); +} + +fn f() u32 { + if (@inComptime()) { + return 1; + } else { + return 2; + } +} + +test "@inComptime" { + try expectEqual(true, comptime @inComptime()); + try expectEqual(false, @inComptime()); + try expectEqual(@as(u32, 1), comptime f()); + try expectEqual(@as(u32, 2), f()); +} +``` +## [类型转化相关 builtin 的重命名]($section.id('类型转化相关 builtin 的重命名')) +之前 `@xToY` 形式的 builtin 现在已经改成了 `@yFromX` ,这样的主要好处是便于阅读(从右向左),这是[草案](https://github.com/ziglang/zig/issues/6128)。 +## [Tuple 类型声明]($section.id('Tuple 类型声明')) +现在可以直接用无 field 名字的 struct 来声明 tuple 类型: +```zig +test "tuple declarations" { + const T = struct { u32, []const u8 }; + var t: T = .{ 1, "foo" }; + try expect(t[0] == 1); + try expectEqualStrings(t[1], "foo"); + + var mul = t ** 3; + try expect(@TypeOf(mul) != T); + try expect(mul.len == 6); + try expect(mul[2] == 1); + try expectEqualStrings(mul[3], "foo"); + + var t2: T = .{ 2, "bar" }; + var cat = t ++ t2; + try expect(@TypeOf(cat) != T); + try expect(cat.len == 4); + try expect(cat[2] == 2); + try expectEqualStrings(cat[3], "bar"); +} +``` + +之前只能用 `std.meta.Tuple` 函数来定义: +```diff +- const testcases = [_]std.meta.Tuple(&[_]type{ []const u8, []const u8, bool }){ ++ const testcases = [_]struct { []const u8, []const u8, bool }{ +``` +## [排序]($section.id('排序')) +现在排序算法分布两类: +- 稳定,blocksort 算法 +- 不稳定,[pdqsort](https://github.com/ziglang/zig/pull/15412) 算法,它结合了随机快速排序的快速平均情况和堆排序的快速最坏情况。 +与堆排的快速最差情况相结合,同时在具有特定模式的输入上达到线性时间。 +## [Stack Unwinding]($section.id('Stack Unwinding')) +Zig 之前依赖 [frame pointer](https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers) 来做堆栈回卷,但它本身有些代价,因此线上环境可能会通过 `-fomit-frame-pointer` 将其禁用掉。 + +为了在这种情况下依然能够获取 panic 是的堆栈信息,Zig 现在支持了通过 DWARF unwind tables 和 MachO compact unwind information 来会恢复堆栈,详见:[#15823](https://github.com/ziglang/zig/pull/15823)。 +## [包管理]($section.id('包管理')) +0.11 首次正式引入了包管理器,具体解释可以参考:[Zig Build System](https://en.liujiacai.net/2023/04/13/zig-build-system/),而且很重要一点,step 之间可以[并发执行](https://ziglang.org/download/0.11.0/release-notes.html#Steps-Run-In-Parallel), +## [Bootstrapping]($section.id('Bootstrapping')) +C++ 实现的 Zig 编译器已经被彻底移除,这意味着 `-fstage1` 不再生效,Zig 现在只需要一个 2.4M 的 WebAssembly 文件和一个 C 编辑器即可,工作细节可以参考:[Goodbye to the C++ Implementation of Zig](https://ziglang.org/news/goodbye-cpp/)。 +## [代码生成]($section.id('代码生成')) +虽然 Zig 编译器现在还是主要使用 LLVM 来进行代码生成,但在这次发布中,其他几个后端也有了非常大的进步: +1. C 后端,行为测试通过 98%,而且生成的 C 代码兼容微软的 MSVC,用在了 bootstrapping 中 +2. x86 后端,行为测试通过 88% +3. aarch64 后端,刚开始 +4. WebAssembly 后端,行为测试通过 86%, +5. SPIR-V 后端,SPIR-V 是在 GPU 上运行的着色器(shader)和内核的字节码表示法。目前,Zig 的 SPIR-V 后端专注于为 OpenCL 内核生成代码,不过未来可能也会支持兼容 Vulkan 的着色器。 +## [增量编译]($section.id('增量编译')) +虽然这仍是一个高度 WIP 的功能,但这一版本周期中的许多改进为编译器的增量编译功能铺平了道路。其中最重要的是 [InternPool](https://github.com/ziglang/zig/pull/15569)。Zig 用户大多看不到这一改动,但它为编译器带来了许多好处,其中之一就是我们现在更接近增量编译了。增量编译将是 0.12.0 发布周期的重点。 +# [观点/教程]($section.id('观点/教程')) +- [Error Handling In Zig](https://www.aolium.com/karlseguin/4013ac14-2457-479b-e59b-e603c04673c8) :: 又一篇讨论错误处理的文章 +- [Commiting Type Crimes in Zig](https://www.1a-insec.net/blog/10-type-magic-in-zig/) :: 对 Zig 类型系统的另一种用法,有些和[邱奇数](https://zh.wikipedia.org/wiki/%E9%82%B1%E5%A5%87%E6%95%B0)类似。 +- [Zig in 100 Seconds](https://www.youtube.com/watch?v=kxT8-C1vmd4) :: Zig 宣传视频 +- [Zig Build System & How to Build Software From Source • Andrew Kelley • GOTO 2023](https://www.youtube.com/watch?v`vKKTMBoxpS8) :: Andrew 关于构建系统的视频,[B 站链接](https://www.bilibili.com/video/BV1Mh4y1K7yc/)、[Youbute](https://www.youtube.com/watch?v`vKKTMBoxpS8) +- [Wrap your NIF with Zig](https://rbino.com/posts/wrap-your-nif-with-zig/) :: NIF 是 Elixir 中进行 FFI 调用的方式,如果用原生 C 接口来用,会需要写很多胶水代码, + 作者这里用 comptime 特性来定义了一个 `make_nif_wrapper` 来简化 NIF 的实现,这个技巧在与 C 项目交互时十分有用。 +- [Types and the Zig Programming Language](https://matklad.github.io/2023/08/09/types-and-zig.html) :: matklad 对 Zig 类型系统的总结 +- [So Long, Twitter and Reddit](https://andrewkelley.me/post/goodbye-twitter-reddit.html) :: Andrew 的最新文章,远离社交平台! +- [WTF is Zig Comptime (and Inline)](https://zig.news/edyu/wtf-is-zig-comptime-and-inline-257b) :: +- [Taking off with Zig: Putting the Z in Benchmark — Double Trouble](https://double-trouble.dev/post/zbench/) :: +# [项目/工具]($section.id('项目/工具')) +- [Mach v0.2 released](https://devlog.hexops.com/2023/mach-v0.2-released/) +# [Zig 语言更新]($section.id('zig-update')) +[2023-07-01..2023-08-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-07-01..2023-08-01) diff --git a/content/monthly/202309.org b/content/monthly/202309.org deleted file mode 100644 index 550fa18..0000000 --- a/content/monthly/202309.org +++ /dev/null @@ -1,55 +0,0 @@ -#+TITLE: 202309 | Bun 正式发布 1.0 -#+DATE: 2023-09-23T10:10:58+0800 -#+LASTMOD: 2023-10-04T20:12:55+0800 -* 重大事件 -** [[https://ziglang.org/news/bounties-damage-open-source-projects/][Bounties Damage Open Source Projects]] -在 2023-09-11 号,Wasmerio CEO 创建了 [[https://github.com/ziglang/zig/issues/17115][Support WASIX · Issue #17115]],表示想赞助 Zig 开发者,让其更好地支持 WASIX 平台。 - -Andrew 与 Loris 在这篇文章中主要阐述了这么做为什么是伤害社区的行为: -1. 助长竞争,牺牲合作 -2. 在软件开发的商业管理方面,悬赏是一种极为简单的方法,这可能让开发者关注短期效益,忽视长期利益,比如维护成本。 - -这篇文章其实很符合 Andrew 的理念,不想让过多的热钱涌入 Zig 社区,他更想保证 Zig 的独立性,这也是他们创办 [[https://kristoff.it/blog/the-open-source-game/][Software You Can Love]] 的初衷。 - -** [[https://bun.sh/blog/bun-v1.0][Bun 1.0]] -面包终于出炉了!Bun 毫无疑问是 Zig 的明星项目,它在 2023-09-08 正式发布了 1.0 版本,这对开发者来说可能没什么太大的区别,毕竟就只是个 tag 而已,但是对于广大的用户来说,这无疑意味着可以在生产环境中去使用了。 - -Bun 并不简单的是个 Node.js 替代品,而是大而全的工具链: -- Transpilers,可以直接运行 =js= =jsx= =mjs= =ts= =tsx= =cjs= 文件 -- Bundlers,可以直接替代 webpack、esbuild 等工具 -- Package managers,兼容 npm,识别 =package.json= 格式,可以替代:npm、yarn、 -- Testing libraries,内置 test runner,支持快照测试、模拟和代码覆盖,可以替代:jest、ts-test -* 观点/教程 -- [[https://linus.dev/posts/kiesel-devlog-1/][Kiesel Devlog #1: Now passing 25% of test262]] :: 另一个 devlog,作者写了一个 JS engine 用来学习 Zig,该作者是 SerenityOS 系统中,Ladybird 浏览器 JS 引擎 LibJS 的作者 -- [[https://mitchellh.com/writing/ghostty-and-useful-zig-patterns][Talk: Introducing Ghostty and Some Useful Zig Patterns]] :: Mitchell Hashimoto 在这篇文章里分享了开发终端 Ghostty 时用的 Zig 常用模式。计划在 2024 年发布 1.0 - - Comptime Interface - - Comptime Data Table Generation - - Comptime Type Generation - - [[https://www.bilibili.com/video/BV1884y1D7gu/][B 站视频地址]] -- [[https://www.openmymind.net/learning_zig/][Learning Zig]] :: 一个 Zig 教程,写的非常易懂,推荐每个 Zig 爱好者阅读。目录 - 1. Language Overview - Part 1 - 2. Language Overview - Part 2 - 3. Style guide - 4. Pointers - 5. Stack Memory - 6. Heap Memory & Allocators - 7. Generics - 8. Coding In Zig - 9. Conclusion -- [[https://zinascii.com/2023/debugging-a-zig-test-failure.html][Debugging a Zig Test Failure]] :: 非常硬核的文章,作者为了调查一个文件名太长的错误,使用了 DTrace 来探测内核函数的调用,对平时的问题排查非常有帮助 -- [[https://notes.eatonphil.com/2023-10-01-intercepting-and-modifying-linux-system-calls-with-ptrace.html][Intercepting and modifying Linux system calls with ptrace]] :: 用 Zig 来封装 Dtrace,用于跟踪子进程的 syscall -- [[https://double-trouble.dev/post/zvm/][Managing Zig Versions with zvm: A Technical Dive]] :: 另一个 Zig 版本管理工具,不过这次是用 Zig 写的了 -- [[https://alic.dev/blog/dense-enums][When Zig Outshines Rust -- Memory Efficient Enum Arrays]] :: 一个图文并茂的文章,重点推荐。作者这里通过分析 array of struct 的内存浪费情况,介绍了 Rust/Zig 中不同的解法,Zig 由于有强大的编译期元编程能力,能够更方便的实现 SoA(struct of arrays) - {{< figure src="/images/struct-padding.webp" caption="struct 数组的内存布局">}} - {{< figure src="/images/soa-layout.webp" caption="SoA 内存布局">}} -* 项目/工具 -- [[https://plugins.jetbrains.com/plugin/22456-zigbrains][ZigBrains]] :: A multifunctional Zig Programming Language plugin for the IDEA platform. -- [[https://github.com/fulcrum-so/ziggy-pydust][fulcrum-so/ziggy-pydust]] :: A toolkit for building Python extensions in Zig. -- [[https://github.com/jiacai2050/zig-curl][zig-curl]] :: Zig bindings to libcurl. -- [[https://github.com/darkr4y/OffensiveZig][darkr4y/OffensiveZig]] :: Some attempts at using [[https://ziglang.org/][Zig]] in penetration testing. -- [[https://wasmlabs.dev/articles/zig-support-on-wasm-workers-server/][Announcing Zig support for Wasm Workers Server]] :: Wasm Workers Server 是一个用于开发 serverless 应用的框架,近期增加了对 Zig 的支持,[[https://workers.wasmlabs.dev/docs/languages/zig][使用文档]]。 -- [[https://github.com/dantecatalfamo/wireguard-config-manager][dantecatalfamo/wireguard]] :: Command line wireguard configuration manager. -- [[https://github.com/iacore/libredo][iacore/libredo]] :: Reactive signal/Dependency tracking library in Zig. -- [[https://github.com/buzz-language/buzz][buzz-language/buzz]] :: A small/lightweight statically typed scripting language -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-08-01..2023-09-01][Zig 语言更新]] -- [[https://github.com/ziglang/zig/pull/17221][Aro translate]] :: 用 Zig 写的 [[https://github.com/Vexu/arocc][Aro]] 来替换 clang,来实现 =translate-c= 的功能 diff --git a/content/monthly/202309.smd b/content/monthly/202309.smd new file mode 100644 index 0000000..9d64b17 --- /dev/null +++ b/content/monthly/202309.smd @@ -0,0 +1,61 @@ +--- +.title = "202309 | Bun 正式发布 1.0", +.date = @date("2023-09-23T10:10:58+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2023-10-04T20:12:55+0800" }, +--- + +# [重大事件]($section.id('重大事件')) +## [[Bounties Damage Open Source Projects](https://ziglang.org/news/bounties-damage-open-source-projects/)]($section.id('[Bounties Damage Open Source Projects](https://ziglang.org/news/bounties-damage-open-source-projects/)')) +在 2023-09-11 号,Wasmerio CEO 创建了 [Support WASIX · Issue #17115](https://github.com/ziglang/zig/issues/17115),表示想赞助 Zig 开发者,让其更好地支持 WASIX 平台。 + +Andrew 与 Loris 在这篇文章中主要阐述了这么做为什么是伤害社区的行为: +1. 助长竞争,牺牲合作 +2. 在软件开发的商业管理方面,悬赏是一种极为简单的方法,这可能让开发者关注短期效益,忽视长期利益,比如维护成本。 + +这篇文章其实很符合 Andrew 的理念,不想让过多的热钱涌入 Zig 社区,他更想保证 Zig 的独立性,这也是他们创办 [Software You Can Love](https://kristoff.it/blog/the-open-source-game/) 的初衷。 + +## [[Bun 1.0](https://bun.sh/blog/bun-v1.0)]($section.id('[Bun 1.0](https://bun.sh/blog/bun-v1.0)')) +面包终于出炉了!Bun 毫无疑问是 Zig 的明星项目,它在 2023-09-08 正式发布了 1.0 版本,这对开发者来说可能没什么太大的区别,毕竟就只是个 tag 而已,但是对于广大的用户来说,这无疑意味着可以在生产环境中去使用了。 + +Bun 并不简单的是个 Node.js 替代品,而是大而全的工具链: +- Transpilers,可以直接运行 `js` `jsx` `mjs` `ts` `tsx` `cjs` 文件 +- Bundlers,可以直接替代 webpack、esbuild 等工具 +- Package managers,兼容 npm,识别 `package.json` 格式,可以替代:npm、yarn、 +- Testing libraries,内置 test runner,支持快照测试、模拟和代码覆盖,可以替代:jest、ts-test +# [观点/教程]($section.id('观点/教程')) +- [Kiesel Devlog #1: Now passing 25% of test262](https://linus.dev/posts/kiesel-devlog-1/) :: 另一个 devlog,作者写了一个 JS engine 用来学习 Zig,该作者是 SerenityOS 系统中,Ladybird 浏览器 JS 引擎 LibJS 的作者 +- [Talk: Introducing Ghostty and Some Useful Zig Patterns](https://mitchellh.com/writing/ghostty-and-useful-zig-patterns) :: Mitchell Hashimoto 在这篇文章里分享了开发终端 Ghostty 时用的 Zig 常用模式。计划在 2024 年发布 1.0 + - Comptime Interface + - Comptime Data Table Generation + - Comptime Type Generation + - [B 站视频地址](https://www.bilibili.com/video/BV1884y1D7gu/) +- [Learning Zig](https://www.openmymind.net/learning_zig/) :: 一个 Zig 教程,写的非常易懂,推荐每个 Zig 爱好者阅读。目录 + 1. Language Overview - Part 1 + 2. Language Overview - Part 2 + 3. Style guide + 4. Pointers + 5. Stack Memory + 6. Heap Memory & Allocators + 7. Generics + 8. Coding In Zig + 9. Conclusion +- [Debugging a Zig Test Failure](https://zinascii.com/2023/debugging-a-zig-test-failure.html) :: 非常硬核的文章,作者为了调查一个文件名太长的错误,使用了 DTrace 来探测内核函数的调用,对平时的问题排查非常有帮助 +- [Intercepting and modifying Linux system calls with ptrace](https://notes.eatonphil.com/2023-10-01-intercepting-and-modifying-linux-system-calls-with-ptrace.html) :: 用 Zig 来封装 Dtrace,用于跟踪子进程的 syscall +- [Managing Zig Versions with zvm: A Technical Dive](https://double-trouble.dev/post/zvm/) :: 另一个 Zig 版本管理工具,不过这次是用 Zig 写的了 +- [When Zig Outshines Rust -- Memory Efficient Enum Arrays](https://alic.dev/blog/dense-enums) :: 一个图文并茂的文章,重点推荐。作者这里通过分析 array of struct 的内存浪费情况,介绍了 Rust/Zig 中不同的解法,Zig 由于有强大的编译期元编程能力,能够更方便的实现 SoA(struct of arrays) + {{< figure src`"/images/struct-padding.webp" caption`"struct 数组的内存布局">}} + {{< figure src`"/images/soa-layout.webp" caption`"SoA 内存布局">}} +# [项目/工具]($section.id('项目/工具')) +- [ZigBrains](https://plugins.jetbrains.com/plugin/22456-zigbrains) :: A multifunctional Zig Programming Language plugin for the IDEA platform. +- [fulcrum-so/ziggy-pydust](https://github.com/fulcrum-so/ziggy-pydust) :: A toolkit for building Python extensions in Zig. +- [zig-curl](https://github.com/jiacai2050/zig-curl) :: Zig bindings to libcurl. +- [darkr4y/OffensiveZig](https://github.com/darkr4y/OffensiveZig) :: Some attempts at using [Zig](https://ziglang.org/) in penetration testing. +- [Announcing Zig support for Wasm Workers Server](https://wasmlabs.dev/articles/zig-support-on-wasm-workers-server/) :: Wasm Workers Server 是一个用于开发 serverless 应用的框架,近期增加了对 Zig 的支持,[使用文档](https://workers.wasmlabs.dev/docs/languages/zig)。 +- [dantecatalfamo/wireguard](https://github.com/dantecatalfamo/wireguard-config-manager) :: Command line wireguard configuration manager. +- [iacore/libredo](https://github.com/iacore/libredo) :: Reactive signal/Dependency tracking library in Zig. +- [buzz-language/buzz](https://github.com/buzz-language/buzz) :: A small/lightweight statically typed scripting language +# [Zig 语言更新]($section.id('zig-update')) +[2023-08-01..2023-09-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-08-01..2023-09-01) diff --git a/content/monthly/202310.org b/content/monthly/202310.org deleted file mode 100644 index 365ff73..0000000 --- a/content/monthly/202310.org +++ /dev/null @@ -1,79 +0,0 @@ -#+TITLE: 202310 -#+DATE: 2023-10-13T07:53:24+0800 -#+LASTMOD: 2023-11-04T19:47:55+0800 -* 重大事件 -* 观点/教程 -- [[https://registerspill.thorstenball.com/p/notes-from-the-field-learning-zig][Notes From the Field: Learning Zig]] :: Zig 初学者的使用经验分享 -- [[https://dgross.ca/blog/friendly-neighbor-announce/][Friendly Neighbor: A network service for Linux wake-on-demand, written in Zig]] :: 作者在这篇文章中分享了 - 用 Zig 重写之前 Ruby 写的一个网络工具,一方面是减轻资源消耗,另一方面是探索用“低级”语言来写程序。不错的案例分享。 -- [[https://www.openmymind.net/Zig-Interfaces/][Zig Interfaces]] :: 作者介绍了 Zig 中如何实现接口这个经常需要用到的功能。最后的实现也比较巧妙,结合 =anytype= 与 =*anyopaque= - #+begin_src zig -const std = @import("std"); - -const Writer = struct { - // These two fields are the same as before - ptr: *anyopaque, - writeAllFn: *const fn (ptr: *anyopaque, data: []const u8) anyerror!void, - - // This is new - fn init(ptr: anytype) Writer { - const T = @TypeOf(ptr); - const ptr_info = @typeInfo(T); - - const gen = struct { - pub fn writeAll(pointer: *anyopaque, data: []const u8) anyerror!void { - const self: T = @ptrCast(@alignCast(pointer)); - return ptr_info.Pointer.child.writeAll(self, data); - } - }; - - return .{ - .ptr = ptr, - .writeAllFn = gen.writeAll, - }; - } - - // This is the same as before - pub fn writeAll(self: Writer, data: []const u8) !void { - return self.writeAllFn(self.ptr, data); - } -}; - -const File = struct { - fd: std.os.fd_t, - - fn writeAll(ptr: *anyopaque, data: []const u8) !void { - const self: *File = @ptrCast(@alignCast(ptr)); - // os.write might not write all of `data`, we should really look at the - // returned value, the number of bytes written, and handle cases where data - // wasn't all written. - _ = try std.os.write(self.fd, data); - } - - fn writer(self: *File) Writer { - return Writer.init(self); - } -}; - -pub fn main() !void { - var file = try std.fs.createFileAbsolute("/tmp/demo.txt", .{}); - var my_file = File{ .fd = file.handle }; - const writer = my_file.writer(); - try writer.writeAll("hello world"); -} - #+end_src -- [[https://notes.eatonphil.com/2023-10-19-write-file-to-disk-with-io_uring.html][io_uring basics: Writing a file to disk]] :: 作者演示了 io_uring 在 Go 与 Zig 中的基本使用,下面表格是一些测试数据 -| method | avg_time | avg_throughput | -|---------------------+----------------------+----------------| -| iouring_128_entries | 0.2756831357s | 3.8GB/s | -| iouring_1_entries | 0.27575404880000004s | 3.8GB/s | -| blocking | 0.2833337046s | 3.7GB/s | -- [[https://www.ryanliptak.com/blog/zig-is-a-windows-resource-compiler/][Zig is now also a Windows resource compiler]] :: 相当硬核的文章,作者最近给 Zig 贡献了一个大功能:支持 Windows 资源定义文件的编译,用户可以通过 =zig rc= 子命令来使用。 -- [[https://zigcc.github.io/post/2023/10/14/zig-version-manager/][Zig 多版本管理]] :: 由于 Zig 还在快速开发迭代中,因此项目很有可能出现新版本 Zig 无法编译的情况,这篇文章介绍了一些管理多个 Zig 版本的方式。 -* 项目/工具 -- [[https://zigcli.liujiacai.net/][zigcli]] :: a toolkit for building command lines programs in Zig -- [[https://github.com/chung-leong/pb2zig][pb2zig]] :: Pixel Bender to Zig code translator -- [[https://github.com/chung-leong/zigar][zigar]] :: Enable the use of Zig code in JavaScript project -- [[https://github.com/jinyus/related_post_gen][jinyus/related_post_gen]] :: 一个对常见语言进行压测的项目,项目里面有几种纯 CPU 的操作,看看哪个语言最快。 -- [[https://github.com/nolanderc/glsl_analyzer][nolanderc/glsl_analyzer]] :: Language server for GLSL (autocomplete, goto-definition, formatter, and more) -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-09-01..2023-10-01][Zig 语言更新]] diff --git a/content/monthly/202310.smd b/content/monthly/202310.smd new file mode 100644 index 0000000..3abbe62 --- /dev/null +++ b/content/monthly/202310.smd @@ -0,0 +1,86 @@ +--- +.title = "202310", +.date = @date("2023-10-13T07:53:24+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2023-11-04T19:47:55+0800" }, +--- + +# [重大事件]($section.id('重大事件')) +# [观点/教程]($section.id('观点/教程')) +- [Notes From the Field: Learning Zig](https://registerspill.thorstenball.com/p/notes-from-the-field-learning-zig) :: Zig 初学者的使用经验分享 +- [Friendly Neighbor: A network service for Linux wake-on-demand, written in Zig](https://dgross.ca/blog/friendly-neighbor-announce/) :: 作者在这篇文章中分享了 + 用 Zig 重写之前 Ruby 写的一个网络工具,一方面是减轻资源消耗,另一方面是探索用"低级"语言来写程序。不错的案例分享。 +- [Zig Interfaces](https://www.openmymind.net/Zig-Interfaces/) :: 作者介绍了 Zig 中如何实现接口这个经常需要用到的功能。最后的实现也比较巧妙,结合 `anytype` 与 `*anyopaque` + ```zig +const std = @import("std"); + +const Writer = struct { + // These two fields are the same as before + ptr: *anyopaque, + writeAllFn: *const fn (ptr: *anyopaque, data: []const u8) anyerror!void, + + // This is new + fn init(ptr: anytype) Writer { + const T = @TypeOf(ptr); + const ptr_info = @typeInfo(T); + + const gen = struct { + pub fn writeAll(pointer: *anyopaque, data: []const u8) anyerror!void { + const self: T = @ptrCast(@alignCast(pointer)); + return ptr_info.Pointer.child.writeAll(self, data); + } + }; + + return .{ + .ptr = ptr, + .writeAllFn = gen.writeAll, + }; + } + + // This is the same as before + pub fn writeAll(self: Writer, data: []const u8) !void { + return self.writeAllFn(self.ptr, data); + } +}; + +const File = struct { + fd: std.os.fd_t, + + fn writeAll(ptr: *anyopaque, data: []const u8) !void { + const self: *File = @ptrCast(@alignCast(ptr)); + // os.write might not write all of `data`, we should really look at the + // returned value, the number of bytes written, and handle cases where data + // wasn't all written. + _ = try std.os.write(self.fd, data); + } + + fn writer(self: *File) Writer { + return Writer.init(self); + } +}; + +pub fn main() !void { + var file = try std.fs.createFileAbsolute("/tmp/demo.txt", .{}); + var my_file ` File{ .fd ` file.handle }; + const writer = my_file.writer(); + try writer.writeAll("hello world"); +} + ``` +- [io_uring basics: Writing a file to disk](https://notes.eatonphil.com/2023-10-19-write-file-to-disk-with-io_uring.html) :: 作者演示了 io_uring 在 Go 与 Zig 中的基本使用,下面表格是一些测试数据 +| method | avg_time | avg_throughput | +|---------------------+----------------------+----------------| +| iouring_128_entries | 0.2756831357s | 3.8GB/s | +| iouring_1_entries | 0.27575404880000004s | 3.8GB/s | +| blocking | 0.2833337046s | 3.7GB/s | +- [Zig is now also a Windows resource compiler](https://www.ryanliptak.com/blog/zig-is-a-windows-resource-compiler/) :: 相当硬核的文章,作者最近给 Zig 贡献了一个大功能:支持 Windows 资源定义文件的编译,用户可以通过 `zig rc` 子命令来使用。 +- [Zig 多版本管理](https://zigcc.github.io/post/2023/10/14/zig-version-manager/) :: 由于 Zig 还在快速开发迭代中,因此项目很有可能出现新版本 Zig 无法编译的情况,这篇文章介绍了一些管理多个 Zig 版本的方式。 +# [项目/工具]($section.id('项目/工具')) +- [zigcli](https://zigcli.liujiacai.net/) :: a toolkit for building command lines programs in Zig +- [pb2zig](https://github.com/chung-leong/pb2zig) :: Pixel Bender to Zig code translator +- [zigar](https://github.com/chung-leong/zigar) :: Enable the use of Zig code in JavaScript project +- [jinyus/related_post_gen](https://github.com/jinyus/related_post_gen) :: 一个对常见语言进行压测的项目,项目里面有几种纯 CPU 的操作,看看哪个语言最快。 +- [nolanderc/glsl_analyzer](https://github.com/nolanderc/glsl_analyzer) :: Language server for GLSL (autocomplete, goto-definition, formatter, and more) +# [Zig 语言更新]($section.id('zig-update')) +[2023-09-01..2023-10-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-09-01..2023-10-01) diff --git a/content/monthly/202311.org b/content/monthly/202311.org deleted file mode 100644 index 9630196..0000000 --- a/content/monthly/202311.org +++ /dev/null @@ -1,102 +0,0 @@ -#+TITLE: 202311 | 传值或传引用,这是个大问题 -#+DATE: 2023-11-19T18:26:29+0800 -#+LASTMOD: 2023-12-04T09:23:13+0800 -* 重大事件 -本月讨论比较多的就是 [[https://www.1a-insec.net/blog/25-zig-reference-semantics/][Zig May Pass Anything By Reference]] 这篇文章了。 - -它讲述了 Zig 里面一个比较有争议的点,函数的参数到底是传值还是传引用。 -#+begin_src zig -const AAAA = struct { - foo: [100]u32, -}; - -fn aaaaa(a: AAAA, b: *AAAA) void { - b.*.foo[0] = 5; - - std.debug.print("wtf: {}", .{ a.foo[0] }); -} - -pub fn main() !void { - var f: AAAA = undefined; - - f.foo[0] = 0; - - aaaaa(f, &f); -} -#+end_src -上面这个例子修改了 =b= 参数的值,但是打印出来的 =a= 的值也被修改了。 - -传值的好处就是不用担心原值会被修改,传引用的好处就是可以减少数据拷贝的代价。但是 Zig 目前采用的方式是由编译器推导来决定合适的方式。这样的目的是减少程序员负担,但这实际上会给程序带来不确定性,比如上述例子。 - -Andrew 在 [[https://lobste.rs/s/et3ivs/zig_may_pass_anything_by_reference#c_yvfrnq][Lobster]] 上回复了这个问题,确实是一个比较严重的问题,一种解法是分析一个变量有多少个 alias,编译器只在确定没问题时才进行优化,但是分析一个变量的 alias 有多少不是件容易的事。 - -其他语言如 C/C++/Rust 等没有进行这种优化尝试,因此没有这个问题,但是 Zig 作为一个新的语言,想尝试来用一种程序员无感的方式来解决,只是目前还没有想到更完善的方案而已。 - -一些熟悉 Zig zen 的读者可能会觉得这违背了第一条『Communicate intent precisely』,目前来看确实是这样的,而且 core team 老早就意识到这个问题了,感兴趣的读者可以参考: -- [[https://github.com/ziglang/zig/issues/5973][Footgun: hidden pass-by-reference #5973]] -- [[https://github.com/ziglang/zig/issues/12064][Design flaw: Swapping struct fields yields unexpected value #12064]] -* 观点/教程 -- [[https://www.openmymind.net/Zigs-std-json-Parsed/][Zig's std.json.Parsed(T)]] :: 老朋友 openmymind 的文章,这篇文章主要讲述了使用 Zig 中的 json 库序列化后,如何更好的使用返回值,由于有一个 allocator 参数,因此比不能简单的返回 ~T~ ,作者这里定义了一个 ~Managed~ 来解决: - #+begin_src zig -pub fn Managed(comptime T: type) type { - return struct { - value: T, - arena: *std.heap.ArenaAllocator, - - const Self = @This(); - - pub fn fromJson(parsed: std.json.Parsed(T)) Self { - return .{ - .arena = parsed.arena, - .value = parsed.value, - }; - } - - pub fn deinit(self: Self) void { - const arena = self.arena; - const allocator = arena.child_allocator; - arena.deinit(); - allocator.destroy(arena); - } - }; -} - #+end_src -- [[https://re.factorcode.org/2023/11/factor-is-faster-than-zig.html][Factor is faster than Zig!]] :: 一个有意思的案例分享。 -- [[https://www.pierrebeaucamp.com/a-day-with-zig/][A day with Zig]] :: 作者把之前一个 Go 的项目转成 Zig,这里介绍了一些感受, - - 文档缺乏 - - 文件级别导入 -- [[https://registerspill.thorstenball.com/p/zig-zaggin][@fieldParentPtr]] :: 对 ~@fieldParentPtr~ 使用的介绍 -- [[https://sudw1n.gitlab.io/posts/zig-build-docs/][Generating documentation from zig build]] :: 作者在这篇文章里尝试在 zig build 文件中输出文档,目前步骤略微繁琐。 - #+begin_src zig -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const exe = b.addExecutable(.{ - .name = "myprogram", - .root_source_file = .{ .path = "src/main.zig" }, - .target = b.standardTargetOptions(.{}), - .optimize = b.standardOptimizeOption(.{}), - }); - - b.installArtifact(exe); - - const install_docs = b.addInstallDirectory(.{ - .source_dir = exe.getEmittedDocs(), - .install_dir = .prefix, - .install_subdir = "docs", - }); - - const docs_step = b.step("docs", "Copy documentation artifacts to prefix path"); - docs_step.dependOn(&install_docs.step); -} - #+end_src -- [[https://www.youtube.com/watch?v=5_oqWE9otaE][What's Zig got that C, Rust and Go don't have? (with Loris Cro)]] :: [视频] Loris 参与的一档播客 -- [[https://mtlynch.io/notes/zig-call-c-simple/][A Simple Example of Calling a C Library from Zig]] :: 一个简明的教程,演示 Zig 如何引用 C 类库 -- [[https://www.reddit.com/r/Zig/comments/17xd46v/what_is_the_zig_philosophy_on_apis_and_abstraction/][What is the Zig philosophy on APIs and abstraction?]] :: 一个 Reddit 帖子,讨论 Zig 的在 API 设计上的哲学、理念。一个有意思的点是 Zig 不支持私有的字段,全部都是 public 的。Andrew 在 [[https://github.com/ziglang/zig/issues/9909#issuecomment-942686366][Proposal: Private Fields #9909]] 这个 issue 里面讨论过原因,主要根据: - - 一个结构体的抽象,很难保证不泄漏,比如一个类型的 align、size,一个函数是否可以在 comptime 执行 - - 一个包的兼容性,应该由文档来解释 - - 增加私有字段,会增加语言的复杂度,而且这种复杂性本身是完全可以避免的 -* 项目/工具 -- [[https://zig.news/xq/zig-build-explained-part-2-1850][zig build explained -- building C/C++ projects]] :: 经典文章回顾,如何使用 Zig 构建系统编译 C/C++ 项目 -- [[https://github.com/akhildevelops/cudaz][akhildevelops/cudaz]] :: A Zig Cuda wrapper -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-10-01..2023-11-01][Zig 语言更新]] diff --git a/content/monthly/202311.smd b/content/monthly/202311.smd new file mode 100644 index 0000000..494f0af --- /dev/null +++ b/content/monthly/202311.smd @@ -0,0 +1,109 @@ +--- +.title = "202311 | 传值或传引用,这是个大问题", +.date = @date("2023-11-19T18:26:29+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2023-12-04T09:23:13+0800" }, +--- + +# [重大事件]($section.id('重大事件')) +本月讨论比较多的就是 [Zig May Pass Anything By Reference](https://www.1a-insec.net/blog/25-zig-reference-semantics/) 这篇文章了。 + +它讲述了 Zig 里面一个比较有争议的点,函数的参数到底是传值还是传引用。 +```zig +const AAAA = struct { + foo: [100]u32, +}; + +fn aaaaa(a: AAAA, b: *AAAA) void { + b.*.foo[0] = 5; + + std.debug.print("wtf: {}", .{ a.foo[0] }); +} + +pub fn main() !void { + var f: AAAA = undefined; + + f.foo[0] = 0; + + aaaaa(f, &f); +} +``` +上面这个例子修改了 `b` 参数的值,但是打印出来的 `a` 的值也被修改了。 + +传值的好处就是不用担心原值会被修改,传引用的好处就是可以减少数据拷贝的代价。但是 Zig 目前采用的方式是由编译器推导来决定合适的方式。这样的目的是减少程序员负担,但这实际上会给程序带来不确定性,比如上述例子。 + +Andrew 在 [Lobster](https://lobste.rs/s/et3ivs/zig_may_pass_anything_by_reference#c_yvfrnq) 上回复了这个问题,确实是一个比较严重的问题,一种解法是分析一个变量有多少个 alias,编译器只在确定没问题时才进行优化,但是分析一个变量的 alias 有多少不是件容易的事。 + +其他语言如 C/C++/Rust 等没有进行这种优化尝试,因此没有这个问题,但是 Zig 作为一个新的语言,想尝试来用一种程序员无感的方式来解决,只是目前还没有想到更完善的方案而已。 + +一些熟悉 Zig zen 的读者可能会觉得这违背了第一条『Communicate intent precisely』,目前来看确实是这样的,而且 core team 老早就意识到这个问题了,感兴趣的读者可以参考: +- [Footgun: hidden pass-by-reference #5973](https://github.com/ziglang/zig/issues/5973) +- [Design flaw: Swapping struct fields yields unexpected value #12064](https://github.com/ziglang/zig/issues/12064) +# [观点/教程]($section.id('观点/教程')) +- [Zig's std.json.Parsed(T)](https://www.openmymind.net/Zigs-std-json-Parsed/) :: 老朋友 openmymind 的文章,这篇文章主要讲述了使用 Zig 中的 json 库序列化后,如何更好的使用返回值,由于有一个 allocator 参数,因此比不能简单的返回 ~T~ ,作者这里定义了一个 ~Managed~ 来解决: + ```zig +pub fn Managed(comptime T: type) type { + return struct { + value: T, + arena: *std.heap.ArenaAllocator, + + const Self = @This(); + + pub fn fromJson(parsed: std.json.Parsed(T)) Self { + return .{ + .arena = parsed.arena, + .value = parsed.value, + }; + } + + pub fn deinit(self: Self) void { + const arena = self.arena; + const allocator = arena.child_allocator; + arena.deinit(); + allocator.destroy(arena); + } + }; +} + ``` +- [Factor is faster than Zig!](https://re.factorcode.org/2023/11/factor-is-faster-than-zig.html) :: 一个有意思的案例分享。 +- [A day with Zig](https://www.pierrebeaucamp.com/a-day-with-zig/) :: 作者把之前一个 Go 的项目转成 Zig,这里介绍了一些感受, + - 文档缺乏 + - 文件级别导入 +- [@fieldParentPtr](https://registerspill.thorstenball.com/p/zig-zaggin) :: 对 ~@fieldParentPtr~ 使用的介绍 +- [Generating documentation from zig build](https://sudw1n.gitlab.io/posts/zig-build-docs/) :: 作者在这篇文章里尝试在 zig build 文件中输出文档,目前步骤略微繁琐。 + ```zig +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const exe = b.addExecutable(.{ + .name = "myprogram", + .root_source_file ` .{ .path ` "src/main.zig" }, + .target = b.standardTargetOptions(.{}), + .optimize = b.standardOptimizeOption(.{}), + }); + + b.installArtifact(exe); + + const install_docs = b.addInstallDirectory(.{ + .source_dir = exe.getEmittedDocs(), + .install_dir = .prefix, + .install_subdir = "docs", + }); + + const docs_step = b.step("docs", "Copy documentation artifacts to prefix path"); + docs_step.dependOn(&install_docs.step); +} + ``` +- [What's Zig got that C, Rust and Go don't have? (with Loris Cro)](https://www.youtube.com/watch?v=5_oqWE9otaE) :: [视频] Loris 参与的一档播客 +- [A Simple Example of Calling a C Library from Zig](https://mtlynch.io/notes/zig-call-c-simple/) :: 一个简明的教程,演示 Zig 如何引用 C 类库 +- [What is the Zig philosophy on APIs and abstraction?](https://www.reddit.com/r/Zig/comments/17xd46v/what_is_the_zig_philosophy_on_apis_and_abstraction/) :: 一个 Reddit 帖子,讨论 Zig 的在 API 设计上的哲学、理念。一个有意思的点是 Zig 不支持私有的字段,全部都是 public 的。Andrew 在 [Proposal: Private Fields #9909](https://github.com/ziglang/zig/issues/9909#issuecomment-942686366) 这个 issue 里面讨论过原因,主要根据: + - 一个结构体的抽象,很难保证不泄漏,比如一个类型的 align、size,一个函数是否可以在 comptime 执行 + - 一个包的兼容性,应该由文档来解释 + - 增加私有字段,会增加语言的复杂度,而且这种复杂性本身是完全可以避免的 +# [项目/工具]($section.id('项目/工具')) +- [zig build explained -- building C/C++ projects](https://zig.news/xq/zig-build-explained-part-2-1850) :: 经典文章回顾,如何使用 Zig 构建系统编译 C/C++ 项目 +- [akhildevelops/cudaz](https://github.com/akhildevelops/cudaz) :: A Zig Cuda wrapper +# [Zig 语言更新]($section.id('zig-update')) +[2023-10-01..2023-11-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-10-01..2023-11-01) diff --git a/content/monthly/202402.org b/content/monthly/202402.org deleted file mode 100644 index 21d3824..0000000 --- a/content/monthly/202402.org +++ /dev/null @@ -1,38 +0,0 @@ -#+TITLE: 202402 | Zig 2024 Roadmap 新鲜出炉 -#+DATE: 2024-03-04T20:54:50+0800 -#+LASTMOD: 2024-03-06T20:39:20+0800 -* 重大事件 -Andrew 最近在 zigshow 节目中介绍了 Zig 2024 年的规划,主要有以下几点: - -1. 0.12 版本会尽快发布 -2. 编译时间现在太慢,进而导致修 bug 的时间长,因此 core team 会优先解决这个编译时间问题。在这个看板中,有相应的进度,主要是:Ditch LLVM、Incremental Compilation 这两个。 - - 很多人都对 Ditch LLVM 这个事情嗤之以鼻,认为这是不自量力,[[https://github.com/ziglang/zig/issues/16270][这个 issue]] 的讨论也比较多,已经有近 200 条回复,最近 Andrew 增加了[[https://github.com/ziglang/zig/issues/16270#issuecomment-1905107583][一条回复]],引用了 [[https://www.joelonsoftware.com/2001/10/14/in-defense-of-not-invented-here-syndrome/][In Defense of Not-Invented-Here Syndrome]],该文章的核心观点是如果一个技术是一个产品的核心点,那么就应该自己写,因为这样才有核心竞争力,文中的例子是 Excel 团队会自己维护一个 C 编译器。 -3. 异步的支持,目前还有还有不少需要解决的技术难点,比如: - - async 函数挂起时,会保存所有上下文,但是在递归函数里,容易 oom - - 无法推倒出 函数指针 是不是 async fn 的,async fn 与普通 fn 的调用方式是不一样的 -4. Donor Bounties,捐赠性悬赏, -5. 工具链,目前还没精力,只能让社区先来做 - -更多细致总结可以参考:[[https://github.com/orgs/zigcc/discussions/91][Zig Roadmap 2024 - Andrew Kelley #91]] -B 站搬运地址:https://www.bilibili.com/video/BV1UC4y167w9/ -* 观点/教程 -- [[https://www.infoworld.com/article/3713082/fast-growing-zig-tops-stack-overflow-survey-for-highest-paid-programming-language.html][Fast-growing Zig tops Stack Overflow survey for highest-paid programming language]] :: 估计是 Bun 带动的贫富差距?! -- [[https://text.garden/writings/generationalpool.html][Pool with generational references in Zig]] :: 作者在文中介绍了一种有意思的思路:当有很多指针指向同一个对象时,如何不用遍历这些指针就能让其失效,答案是用一个胖指针,包括两部分:真正的数据指针和指向对象的年龄,当指针的年龄和指向对象的年龄不一致时,即认为该指针失效。 -- [[https://lupyuen.codeberg.page/articles/tcc.html][TCC RISC-V Compiler runs in the Web Browser (thanks to Zig Compiler)]] :: [[https://github.com/sellicott/tcc-riscv32][TCC]] 是一个 64 位 RISC-V 的编译器,作者在这篇文章里利用 Zig 把它编译成 WebAssembly 放到浏览器里执行。其中的难点在于 TCC 会调用 Posix 的一些函数,比如:fopen、fprintf、strncpy、malloc 等,但是这些在 WebAssembly 里是没有的,因此需要[[https://github.com/lupyuen/tcc-riscv32-wasm/blob/main/zig/tcc-wasm.zig#L447-L774][自己实现]],不过幸好 Zig 社区内已经有不少 libc 的实现了: - - [[https://github.com/marler8997/ziglibc][marler8997/ziglibc]] - - [[https://github.com/ZigEmbeddedGroup/foundation-libc][ZigEmbeddedGroup/foundation-libc]],A libc implementation written in Zig that is designed to be used with freestanding targets. -- [[https://devlog.hexops.com/2024/building-the-directx-shader-compiler-better-than-microsoft/][Building the DirectX shader compiler better than Microsoft?]] :: -- [[http://coypu.sdf.org/porting-zig.html][Porting Zig to NetBSD]] :: -- [[https://blog.syndica.io/sig-engineering-part-2-accountsdb-more/][Sig Engineering - Part 2 - Progress on AccountsDB & more]] :: Syndica 公司的 Sig,这是一款用 Zig 编写的、专注于 RPS 的 Solana 验证器客户端。在这篇文章里,他们分析了如何优化 HashMap 的性能,而且实现了一个基于本地磁盘的 Allocator。而且他们还在招聘 Zig 工程师: - - [[https://jobs.ashbyhq.com/syndica/15ab4e32-0f32-41a0-b8b0-16b6518158e9][Senior Software Engineer (Zig/C/Rust) @ Syndica]] - -* 项目/工具 -- [[https://github.com/semickolon/kirei][semickolon/kirei]] :: 🌸 The prettiest keyboard software -- [[https://github.com/Dok8tavo/Interfacil/][Dok8tavo/Interfacil]] :: Interfacil is a Zig package for making and using interfaces easily in Zig. -- [[https://github.com/kamlesh-nb/azure-sdk-for-zig][kamlesh-nb/azure-sdk-for-zig]] :: Azure Sdk for Zig - Experimental -- [[https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer][ringtailsoftware/zig-wasm-audio-framebuffer]] :: Examples of integrating Zig and Wasm for audio and graphics on the web -- [[https://github.com/cztomsik/tokamak][cztomsik/tokamak]] :: Server-side framework for Zig, relying heavily on dependency injection. -- [[https://github.com/The-Z-Labs/cli4bofs][The-Z-Labs/cli4bofs]] :: Command line interface for (running) BOFs -- [[https://github.com/nelipuu/zbind][nelipuu/zbind]] :: Zig-TypeScript binding generator 🟦 🦎 -- [[https://github.com/sneekyfoxx/ziggy][sneekyfoxx/ziggy]] :: 又又一个 Zig 版本管理工具 -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2023-03-01][Zig 语言更新]] diff --git a/content/monthly/202402.smd b/content/monthly/202402.smd new file mode 100644 index 0000000..d7425de --- /dev/null +++ b/content/monthly/202402.smd @@ -0,0 +1,45 @@ +--- +.title = "202402 | Zig 2024 Roadmap 新鲜出炉", +.date = @date("2024-03-04T20:54:50+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2024-03-06T20:39:20+0800" }, +--- + +# [重大事件]($section.id('重大事件')) +Andrew 最近在 zigshow 节目中介绍了 Zig 2024 年的规划,主要有以下几点: + +1. 0.12 版本会尽快发布 +2. 编译时间现在太慢,进而导致修 bug 的时间长,因此 core team 会优先解决这个编译时间问题。在这个看板中,有相应的进度,主要是:Ditch LLVM、Incremental Compilation 这两个。 + - 很多人都对 Ditch LLVM 这个事情嗤之以鼻,认为这是不自量力,[这个 issue](https://github.com/ziglang/zig/issues/16270) 的讨论也比较多,已经有近 200 条回复,最近 Andrew 增加了[一条回复](https://github.com/ziglang/zig/issues/16270#issuecomment-1905107583),引用了 [In Defense of Not-Invented-Here Syndrome](https://www.joelonsoftware.com/2001/10/14/in-defense-of-not-invented-here-syndrome/),该文章的核心观点是如果一个技术是一个产品的核心点,那么就应该自己写,因为这样才有核心竞争力,文中的例子是 Excel 团队会自己维护一个 C 编译器。 +3. 异步的支持,目前还有还有不少需要解决的技术难点,比如: + - async 函数挂起时,会保存所有上下文,但是在递归函数里,容易 oom + - 无法推倒出 函数指针 是不是 async fn 的,async fn 与普通 fn 的调用方式是不一样的 +4. Donor Bounties,捐赠性悬赏, +5. 工具链,目前还没精力,只能让社区先来做 + +更多细致总结可以参考:[Zig Roadmap 2024 - Andrew Kelley #91](https://github.com/orgs/zigcc/discussions/91) +B 站搬运地址:https://www.bilibili.com/video/BV1UC4y167w9/ +# [观点/教程]($section.id('观点/教程')) +- [Fast-growing Zig tops Stack Overflow survey for highest-paid programming language](https://www.infoworld.com/article/3713082/fast-growing-zig-tops-stack-overflow-survey-for-highest-paid-programming-language.html) :: 估计是 Bun 带动的贫富差距?! +- [Pool with generational references in Zig](https://text.garden/writings/generationalpool.html) :: 作者在文中介绍了一种有意思的思路:当有很多指针指向同一个对象时,如何不用遍历这些指针就能让其失效,答案是用一个胖指针,包括两部分:真正的数据指针和指向对象的年龄,当指针的年龄和指向对象的年龄不一致时,即认为该指针失效。 +- [TCC RISC-V Compiler runs in the Web Browser (thanks to Zig Compiler)](https://lupyuen.codeberg.page/articles/tcc.html) :: [TCC](https://github.com/sellicott/tcc-riscv32) 是一个 64 位 RISC-V 的编译器,作者在这篇文章里利用 Zig 把它编译成 WebAssembly 放到浏览器里执行。其中的难点在于 TCC 会调用 Posix 的一些函数,比如:fopen、fprintf、strncpy、malloc 等,但是这些在 WebAssembly 里是没有的,因此需要[自己实现](https://github.com/lupyuen/tcc-riscv32-wasm/blob/main/zig/tcc-wasm.zig#L447-L774),不过幸好 Zig 社区内已经有不少 libc 的实现了: + - [marler8997/ziglibc](https://github.com/marler8997/ziglibc) + - [ZigEmbeddedGroup/foundation-libc](https://github.com/ZigEmbeddedGroup/foundation-libc),A libc implementation written in Zig that is designed to be used with freestanding targets. +- [Building the DirectX shader compiler better than Microsoft?](https://devlog.hexops.com/2024/building-the-directx-shader-compiler-better-than-microsoft/) :: +- [Porting Zig to NetBSD](http://coypu.sdf.org/porting-zig.html) :: +- [Sig Engineering - Part 2 - Progress on AccountsDB & more](https://blog.syndica.io/sig-engineering-part-2-accountsdb-more/) :: Syndica 公司的 Sig,这是一款用 Zig 编写的、专注于 RPS 的 Solana 验证器客户端。在这篇文章里,他们分析了如何优化 HashMap 的性能,而且实现了一个基于本地磁盘的 Allocator。而且他们还在招聘 Zig 工程师: + - [Senior Software Engineer (Zig/C/Rust) @ Syndica](https://jobs.ashbyhq.com/syndica/15ab4e32-0f32-41a0-b8b0-16b6518158e9) + +# [项目/工具]($section.id('项目/工具')) +- [semickolon/kirei](https://github.com/semickolon/kirei) :: 🌸 The prettiest keyboard software +- [Dok8tavo/Interfacil](https://github.com/Dok8tavo/Interfacil/) :: Interfacil is a Zig package for making and using interfaces easily in Zig. +- [kamlesh-nb/azure-sdk-for-zig](https://github.com/kamlesh-nb/azure-sdk-for-zig) :: Azure Sdk for Zig - Experimental +- [ringtailsoftware/zig-wasm-audio-framebuffer](https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer) :: Examples of integrating Zig and Wasm for audio and graphics on the web +- [cztomsik/tokamak](https://github.com/cztomsik/tokamak) :: Server-side framework for Zig, relying heavily on dependency injection. +- [The-Z-Labs/cli4bofs](https://github.com/The-Z-Labs/cli4bofs) :: Command line interface for (running) BOFs +- [nelipuu/zbind](https://github.com/nelipuu/zbind) :: Zig-TypeScript binding generator 🟦 🦎 +- [sneekyfoxx/ziggy](https://github.com/sneekyfoxx/ziggy) :: 又又一个 Zig 版本管理工具 +# [Zig 语言更新]($section.id('zig-update')) +[2024-02-01..2024-03-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2024-03-01) diff --git a/content/monthly/202403.org b/content/monthly/202403.smd similarity index 57% rename from content/monthly/202403.org rename to content/monthly/202403.smd index b26e461..8298fd3 100644 --- a/content/monthly/202403.org +++ b/content/monthly/202403.smd @@ -1,40 +1,45 @@ -#+TITLE: 202403 | ziglang.cc 正式上线 -#+DATE: 2024-03-13T20:32:27+0800 -#+LASTMOD: 2024-05-02T22:14:30+0800 -* 重大事件 -#+begin_quote +--- +.title = "202403 | ziglang.cc 正式上线", +.date = @date("2024-03-13T20:32:27+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2024-05-02T22:14:30+0800" }, +--- + +# [重大事件]($section.id('重大事件')) +> https://ziglang.cc/ -#+end_quote -之前 ZigCC 所有项目都是托管在 GitHub 之上,网页基于 Pages 构建,域名自然也就是 github.io 的,虽然 GitHub 提供了很多利于开发者的服务,但过于依赖 GitHub 这种商业公司,还是不利于 ZigCC 的长远发展,域名是其中很重要一个,有了独立域名,网页托管选择就多了,比如 [[https://pages.cloudflare.com/][Cloudflare Pages]]。 -另一个大家比较关心的问题就是 0.12 的发版,虽然 [[https://github.com/ziglang/zig/milestone/23][milestone]] 显示还剩 10 来个 open 的 issue,但是这只是个幌子,核心团队还是有可能随时 delay。不过从剩下的 issue 来分析,主要问题还剩两大类: +之前 ZigCC 所有项目都是托管在 GitHub 之上,网页基于 Pages 构建,域名自然也就是 github.io 的,虽然 GitHub 提供了很多利于开发者的服务,但过于依赖 GitHub 这种商业公司,还是不利于 ZigCC 的长远发展,域名是其中很重要一个,有了独立域名,网页托管选择就多了,比如 [Cloudflare Pages](https://pages.cloudflare.com/)。 + +另一个大家比较关心的问题就是 0.12 的发版,虽然 [milestone](https://github.com/ziglang/zig/milestone/23) 显示还剩 10 来个 open 的 issue,但是这只是个幌子,核心团队还是有可能随时 delay。不过从剩下的 issue 来分析,主要问题还剩两大类: 1. 构建系统完善 2. 修复之前功能带来的回顾问题(regression 这个 tag) -新功能看来是已经 ready 了,但这并不是说剩下的这些工具就好解决了,Andrew 在 [[https://rustacean-station.org/episode/andrew-kelley/][Zig with Andrew Kelley]] 这一期播客里提到的 [[https://zh.wikipedia.org/wiki/90-90%E6%B3%95%E5%88%99][90-90]] 理论很好的解释了这一点: -#+begin_quote +新功能看来是已经 ready 了,但这并不是说剩下的这些工具就好解决了,Andrew 在 [Zig with Andrew Kelley](https://rustacean-station.org/episode/andrew-kelley/) 这一期播客里提到的 [90-90](https://zh.wikipedia.org/wiki/90-90%E6%B3%95%E5%88%99) 理论很好的解释了这一点: +> (开发软件时)前 90% 的代码要花费 90% 的开发时间,剩余的 10% 的代码要再花费 90% 的开发时间。 -#+end_quote 当然,后面 ZigCC 也会紧密关注发布动态,有消息第一时间分享给大家。耐不住寂寞的朋友,可以先去刷刷 Zig 的 discord。 -* 观点/教程 -- [[https://github.com/ziglang/zig/pull/19208][Redesign How Autodoc Works]] :: Andrew 在这个 PR 里重构了现有的文档系统 Autodoc,之前的实现问题很多。比如: - - 很多功能重复的文件,最夸张的是 =lib/docs/ziglexer.js= ,它是用 JS 实现的 Zig 的解析器,其实 Zig 已经在标准库中暴露解析相关 API,通过 wasm 就可以调用 +# [观点/教程]($section.id('观点/教程')) +- [Redesign How Autodoc Works](https://github.com/ziglang/zig/pull/19208) :: Andrew 在这个 PR 里重构了现有的文档系统 Autodoc,之前的实现问题很多。比如: + - 很多功能重复的文件,最夸张的是 `lib/docs/ziglexer.js` ,它是用 JS 实现的 Zig 的解析器,其实 Zig 已经在标准库中暴露解析相关 API,通过 wasm 就可以调用 - 功能更强,因为新设计方案不再处理 ZIR,而是直接处理源文件,这意味着它拥有100% 的信息,不需要向后拼凑任何东西。 - sources.tar 文件经 HTTP 层解压后,直接进入 wasm 模块的内存。使用 std.tar 对 tar 文件进行解析,并对源文件进行就地解析,同时在哈希表中添加一些额外的计算。虽然可以通过 Worker 来加快解析速度,但单线程的解析速度已经非常快,因此这并不是非常有必要。 快来体验最新的文档系统吧:https://ziglang.org/documentation/master/std/ -- [[https://notes.eatonphil.com/2024-03-15-zig-rust-and-other-languages.html][Zig, Rust, and other languages]] :: 老朋友 Phil Eaton 的文章,在这里他针对以下几点进行了语言对比: +- [Zig, Rust, and other languages](https://notes.eatonphil.com/2024-03-15-zig-rust-and-other-languages.html) :: 老朋友 Phil Eaton 的文章,在这里他针对以下几点进行了语言对比: - 内存管理。Zig 最大的问题是不支持 RAII,一个近似的概念是 arenas 分配器。 - - 标准库,主要是讨论标准库是否应该精简为主, =node_modules= 是业界经常提到的一个反面例子,一般支持精简的人会认为, + - 标准库,主要是讨论标准库是否应该精简为主, `node_modules` 是业界经常提到的一个反面例子,一般支持精简的人会认为, - 语言的 std 不容易出现 breaking changes,想 Python 里就有 urllib、urllib2、urllib3 这三个网络库, 但是社区推荐的并不是这三个,而是 requests,这样 std 的位置就有些尴尬 - Zig 目前的标准库算是中等大小,json、compress 压缩等功能都有 - - 显示分配,这算是 Zig 的强项,其他语言很少有支持这个的,因此作者在这建议增加一种类似 =must-not-allocate= 的注解, + - 显示分配,这算是 Zig 的强项,其他语言很少有支持这个的,因此作者在这建议增加一种类似 `must-not-allocate` 的注解, 这样高级语言里,也可以保值某些操作不会有内存分配。 -- [[https://mtlynch.io/zig-extraneous-build/][Why does an extraneous build step make my Zig app 10x faster?]] :: 作者在这篇文章里分享了自己遇到的一个很有意思的问题, +- [Why does an extraneous build step make my Zig app 10x faster?](https://mtlynch.io/zig-extraneous-build/) :: 作者在这篇文章里分享了自己遇到的一个很有意思的问题, 同一份代码,执行方式不同,竟然有不同的耗时。最小复现代码: - #+begin_src zig + ```zig // src/main.zig const std = @import("std"); @@ -67,9 +72,9 @@ pub fn main() !void { try output.print("bytes: {}\n", .{count}); try output.print("execution time: {d:.3}µs\n", .{elapsed_micros}); } - #+end_src + ``` 两种执行方式: - #+begin_src bash + ```bash $ echo '00010203040506070809' | xxd -r -p | zig build run -Doptimize=ReleaseFast bytes: 10 execution time: 13.549µs @@ -77,22 +82,22 @@ execution time: 13.549µs $ echo '00010203040506070809' | xxd -r -p | ./zig-out/bin/count-bytes bytes: 10 execution time: 162.195µs -#+end_src - 可以看到,通过 =zig build run= 的方式来执行时,耗时相比直接执行编译好的二进制要快 10 倍。 - 问题的关键在于 shell 的 pipeline 的执行机制,对于 =A | B= 这样一个简单的 pipeline,一般本能的会认为 B 只会在 A - 执行完后才开始执行,但是实际上它们是同时运行的,因此,在上面的例子里 =main= 函数的执行时间在 =zig build run= 方式下, +``` + 可以看到,通过 `zig build run` 的方式来执行时,耗时相比直接执行编译好的二进制要快 10 倍。 + 问题的关键在于 shell 的 pipeline 的执行机制,对于 `A | B` 这样一个简单的 pipeline,一般本能的会认为 B 只会在 A + 执行完后才开始执行,但是实际上它们是同时运行的,因此,在上面的例子里 `main` 函数的执行时间在 `zig build run` 方式下, 其实执行的要晚一些,因为它需要先执行编译操作,因此造成了这个误差。 -- [[https://neurobug.com/posts/zig/billion/][One Bilion rows in zig]] :: 作者用[[https://1brc.dev/][ 1BRC]] 这个项目作为 Zig 的练手项目,里面用到了 [[https://github.com/mstange/samply][mstange/samply]] 这个 Profiler +- [One Bilion rows in zig](https://neurobug.com/posts/zig/billion/) :: 作者用[ 1BRC](https://1brc.dev/) 这个项目作为 Zig 的练手项目,里面用到了 [mstange/samply](https://github.com/mstange/samply) 这个 Profiler 工具,还起来还比较实用。 -- [[https://matklad.github.io/2024/03/21/defer-patterns.html][Zig defer Patterns]] :: Matklad 最新的一篇文章,Ziggit [[https://ziggit.dev/t/zig-defer-patterns/3638/3][讨论链接]]。里面讲述了 defer 除了做资源回收外,其他的一些惯用法,里面有几个有趣的点: - #+begin_src zig +- [Zig defer Patterns](https://matklad.github.io/2024/03/21/defer-patterns.html) :: Matklad 最新的一篇文章,Ziggit [讨论链接](https://ziggit.dev/t/zig-defer-patterns/3638/3)。里面讲述了 defer 除了做资源回收外,其他的一些惯用法,里面有几个有趣的点: + ```zig errdefer comptime unreachable - #+end_src - 文中称这个是 Zig 的巅峰用法😅, =errdefer unreachable= 还比较好理解,即在执行出错时,执行 unreachable ,加上 comptime 呢? + ``` + 文中称这个是 Zig 的巅峰用法😅, `errdefer unreachable` 还比较好理解,即在执行出错时,执行 unreachable ,加上 comptime 呢? 其实这是阻止 Zig 编译器生产错误处理的代码,即在编译时期保证下面的逻辑不会出错,确实用的很巧妙!一个简单的例子: - #+begin_src zig + ```zig const std = @import("std"); test "errdeferWithUnreachable" { @@ -107,16 +112,16 @@ fn inc(a: i8) !i8 { } return a + 1; } - #+end_src - 直接执行 =zig test= ,在编译时会报下面的错误: -#+begin_src shell + ``` + 直接执行 `zig test` ,在编译时会报下面的错误: +```bash test.zig:4:23: error: reached unreachable code errdefer comptime unreachable; -#+end_src -虽然 =a= 是个运行时的值,但是 =errdefer comptime unreachable= 不关心这个,只要 Zig 编译器开始生成 ErrorSet 相关代码, +``` +虽然 `a` 是个运行时的值,但是 `errdefer comptime unreachable` 不关心这个,只要 Zig 编译器开始生成 ErrorSet 相关代码, 编译就会报错,去掉上面的 if 代码块后,测试就可以正常执行。一个实际的例子: -- [[https://github.com/ziglang/zig/pull/19364/files][std.hash_map: fix pointer lock safety false positive by andrewrk · Pull Request #19364 · ziglang/zig]] -#+begin_src diff +- [std.hash_map: fix pointer lock safety false positive by andrewrk · Pull Request #19364 · ziglang/zig](https://github.com/ziglang/zig/pull/19364/files) +```diff assert(std.math.isPowerOfTwo(new_cap)); var map: Self = .{}; @@ -130,7 +135,7 @@ test.zig:4:23: error: reached unreachable code @@6581,7 @@ pub fn HashMapUnmanaged( self.size = 0; - self.pointer_stability = .{ .state = .unlocked }; + self.pointer_stability ` .{ .state ` .unlocked }; std.mem.swap(Self, self, &map); + map.deinit(allocator); @@ -139,24 +144,24 @@ test.zig:4:23: error: reached unreachable code + var map: std.StringHashMapUnmanaged(void) = .{}; + try testing.expectError(error.OutOfMemory, map.getOrPut(std.testing.failing_allocator, "hello")); +} -#+end_src -可以看到, 这么修改后,就可以保证 =map.deinit(allocator)= 语句之前没有错误可能产生!读者可以细细品味一下这个用法。 +``` +可以看到, 这么修改后,就可以保证 `map.deinit(allocator)` 语句之前没有错误可能产生!读者可以细细品味一下这个用法。 另一个小技巧是 errdefer 竟然支持错误捕获,即下面这种用法: - #+begin_src zig + ```zig const port = port: { errdefer |err| std.log.err("failed to read the port number: {!}", .{err}); var buf: [fmt.count("{}\n", .{maxInt(u16)})]u8 = undefined; const len = try process.stdout.?.readAll(&buf); break :port try fmt.parseInt(u16, buf[0 .. len -| 1], 10); }; - #+end_src - - [[https://ziggit.dev/t/build-system-tricks/3531/1][Build system tricks]] :: 介绍了 zig build 的使用技巧,这些技巧有助于在确保方便地命名和布局构建步骤的同时,如何使用构建系统的每个部分。 - - [[https://blog.mjgrzymek.com/blog/zigwasm][Using Zig with WebAssembly]] :: 如何将 Zig 编译成 wasm,并传递复杂的参数。 + ``` + - [Build system tricks](https://ziggit.dev/t/build-system-tricks/3531/1) :: 介绍了 zig build 的使用技巧,这些技巧有助于在确保方便地命名和布局构建步骤的同时,如何使用构建系统的每个部分。 + - [Using Zig with WebAssembly](https://blog.mjgrzymek.com/blog/zigwasm) :: 如何将 Zig 编译成 wasm,并传递复杂的参数。 -* 项目/工具 -- [[https://github.com/xataio/pgzx][xataio/pgzx]] :: Create PostgreSQL extensions using Zig. 一个例子: - #+begin_src zig +# [项目/工具]($section.id('项目/工具')) +- [xataio/pgzx](https://github.com/xataio/pgzx) :: Create PostgreSQL extensions using Zig. 一个例子: + ```zig const std = @import("std"); const pgzx = @import("pgzx"); @@ -183,18 +188,19 @@ fn char_count_zig(input_text: []const u8, target_char: []const u8) !u32 { } return count; } - #+end_src + ``` -- [[https://github.com/NoelJacob/zman][Manage Zig installations]] :: 又又又叒一个 Zig 管理工具,Rust 开发。 - #+begin_src bash +- [Manage Zig installations](https://github.com/NoelJacob/zman) :: 又又又叒一个 Zig 管理工具,Rust 开发。 + ```bash zman default latest zman default master zman default 0.12.0 - #+end_src + ``` -- [[https://github.com/mahdifrmz/qooil][mahdifrmz/qooil]] :: 用 Zig 语言编写的文件传输软件 -- [[https://github.com/timfayz/pretty][timfayz/pretty]] :: Pretty printer for arbitrary data structures in Zig -- [[https://github.com/liyu1981/zcmd.zig][liyu1981/zcmd.zig]] :: Zcmd is a single file lib to replace zig's std.childProcess.run with the ability of running pipeline like bash. -- [[https://github.com/zigcc/zig-milestone][zigcc/zig-milestone]] :: Zig milstone monitor +- [mahdifrmz/qooil](https://github.com/mahdifrmz/qooil) :: 用 Zig 语言编写的文件传输软件 +- [timfayz/pretty](https://github.com/timfayz/pretty) :: Pretty printer for arbitrary data structures in Zig +- [liyu1981/zcmd.zig](https://github.com/liyu1981/zcmd.zig) :: Zcmd is a single file lib to replace zig's std.childProcess.run with the ability of running pipeline like bash. +- [zigcc/zig-milestone](https://github.com/zigcc/zig-milestone) :: Zig milstone monitor -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2024-03-01][Zig 语言更新]] +# [Zig 语言更新]($section.id('zig-update')) +[2024-02-01..2024-03-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2024-03-01) diff --git a/content/monthly/202404.org b/content/monthly/202404.org deleted file mode 100644 index 20dc3e0..0000000 --- a/content/monthly/202404.org +++ /dev/null @@ -1,76 +0,0 @@ -#+TITLE: 202404 | Zig 0.12.0 正式释出 -#+DATE: 2024-04-18T21:59:49+0800 -#+LASTMOD: 2024-05-03T09:01:56+0800 -* 重大事件 -千呼万唤的 0.12.0 版本终于 2024-04-20 正式释出了!这次版本历时 8 个月,有 268 位贡献者,一共进行了 3688 次提交!社区内的一些讨论:[[https://news.ycombinator.com/item?id=40096176][Hacker News]]、[[https://lobste.rs/s/fa4svu][Lobsters]]。 -这是它的 [[https://ziglang.org/download/0.12.0/release-notes.html][Release notes]]。ZigCC 对这个文档进行了翻译、整理,方便大家阅读: -- [[https://course.ziglang.cc/update/upgrade-0.12.0][0.12.0 升级指南]] -- [[https://course.ziglang.cc/update/0.12.0-description][0.12.0 版本说明]] - -并且还在 2024-04-27 举行了一次线上的 meetup 来庆祝这次发布,这是会议的总结:[[https://ziglang.cc/news/2024/04/27/release-party-review/][0.12.0 Release Party 回顾]]。 - -0.12.0 这个版本,对用户来说,最重大的变更就是构建系统的稳定了,这对于 Zig 生态的发展是十分关键的一步,试想一个项目用到的依赖之间版本不兼容, -这是十分痛苦的事情,毫无疑问这是阻碍 Zig 生态发生的绊脚石,没有之一。好在这一切都在 0.12 这个版本解决了,用户可以基于 Step -构成的有向无环图来编译自己的项目,不需要再折腾 CMake、Makefile、Vcpkg、Git submodule 等工具,所有的依赖使用 zon 来管理即可。 -读者如果对 Zig 构建系统还不熟悉,可以参考: -- 官方文档:[[https://ziglang.org/learn/build-system/][Zig Build System]] -- Zig 升级: [[https://course.ziglang.cc/engineering/build-system][构建系统]] - -期待一年后 Zig 的生态! -* 观点/教程 -- [[https://github.com/zigcc/forum/issues/112][Zig 中任意精度整数用途与实现]] :: 由于 CPU 在访问内存时,一般都会有对齐的要求,对于这种非常规的数字,在内存中的地址会是怎样的呢?可以做一个简单的实验: - - #+begin_src zig -const std = @import("std"); - -const Foo = packed struct { - a: u3, - b: u2, -}; - -pub fn main() !void { - const vs = [_]u3{ 1, 2, 3 }; - for (&vs) |*b| { - std.debug.print("{p}-{b}\n", .{ b, b.* }); - } - - std.debug.print("U3 size: {d}\n", .{@sizeOf(u3)}); - std.debug.print("Foo size: {d}\n", .{@sizeOf(Foo)}); - - const foos = [_]Foo{ - .{ .a = 1, .b = 3 }, - }; - - std.debug.print("foo as bytes: {b}\n", .{std.mem.sliceAsBytes(&foos)}); - - for (foos) |b| { - std.debug.print("{any}-{any}\n", .{ &b.a, &b.b }); - } -} - #+end_src - 输出: - #+begin_src bash -u3@104d11a2c-1 -u3@104d11a2d-10 -u3@104d11a2e-11 -U3 size: 1 -Foo size: 1 -foo as bytes: { 11001 } -u3@16b196367-u2@16b196367 - #+end_src - - 通过前三个输出可以知道,每个 u3 实际占用一个字节,但当用在 packed 结构中,就会变成 3 个 bit。其中的 11001 就是字段 a b 混合后的值,且 a 是三位,b 是高两位。 -- [[https://procmarco.com/blog/learnings-from-building-a-db-in-zig/][Learnings From Building a DB in Zig]] :: 作者分享了在一次 3 天的 Hackthon 中,使用 Zig 开发一个数据库的经历。 -- [[https://zig.news/michalsieron/buildzigzon-dependency-hashes-47kj][build.zig.zon dependency hashes]] :: 讲解了 zon 中依赖的 hash 是怎么计算出来的 -- [[https://zig.news/liyu1981/play-with-new-comptime-var-rule-of-zig-0120-333k][play with new comptime var rule of zig 0.12.0]] :: -- [[https://zig.news/inspectorboat/to-simd-and-beyond-optimizing-a-simple-comparison-routine-1jkf][To SIMD and beyond: Optimizing a simple comparison routine]] :: 作者在这里循序渐进的介绍了几种数字比较的技巧,从基本的方案,到 Vector,到最后利用 bit 的特点,来逐步优化,并用 godbolt 查看生成的汇编代码,是一篇不错的文章。 -- [[https://www.reddit.com/r/Zig/comments/1cc1x2v/documentation_takes_another_step_backwards/][Documentation takes another step backwards : r/Zig]] :: 一个 Reddit 用户对文档的抱怨 -* 项目/工具 -- [[https://github.com/rofrol/zig-companies][rofrol/zig-companies]] :: A list of companies using Zig in production. -- [[https://github.com/akarpovskii/tuile][akarpovskii/tuile]] :: A Text UI library for Zig -- [[https://codeberg.org/mntnmntn/zenith][mntnmntn/zenith]] :: A very minimal text editor in Zig,支持 0.12.0 版本 -- [[https://github.com/chung-leong/zigar][chung-leong/zigar]] :: Enable the use of Zig code in JavaScript project -- [[https://github.com/jnordwick/zig-string][jnordwick/zig-string]] :: Zig string library that includes small string optimization on the stack -- [[https://github.com/FalsePattern/ZigBrains][FalsePattern/ZigBrains]] :: Yet another zig language plugin for intellij - -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-04-01..2024-05-01][Zig 语言更新]] diff --git a/content/monthly/202404.smd b/content/monthly/202404.smd new file mode 100644 index 0000000..030b7d2 --- /dev/null +++ b/content/monthly/202404.smd @@ -0,0 +1,83 @@ +--- +.title = "202404 | Zig 0.12.0 正式释出", +.date = @date("2024-04-18T21:59:49+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2024-05-03T09:01:56+0800" }, +--- + +# [重大事件]($section.id('重大事件')) +千呼万唤的 0.12.0 版本终于 2024-04-20 正式释出了!这次版本历时 8 个月,有 268 位贡献者,一共进行了 3688 次提交!社区内的一些讨论:[Hacker News](https://news.ycombinator.com/item?id=40096176)、[Lobsters](https://lobste.rs/s/fa4svu)。 +这是它的 [Release notes](https://ziglang.org/download/0.12.0/release-notes.html)。ZigCC 对这个文档进行了翻译、整理,方便大家阅读: +- [0.12.0 升级指南](https://course.ziglang.cc/update/upgrade-0.12.0) +- [0.12.0 版本说明](https://course.ziglang.cc/update/0.12.0-description) + +并且还在 2024-04-27 举行了一次线上的 meetup 来庆祝这次发布,这是会议的总结:[0.12.0 Release Party 回顾](https://ziglang.cc/news/2024/04/27/release-party-review/)。 + +0.12.0 这个版本,对用户来说,最重大的变更就是构建系统的稳定了,这对于 Zig 生态的发展是十分关键的一步,试想一个项目用到的依赖之间版本不兼容, +这是十分痛苦的事情,毫无疑问这是阻碍 Zig 生态发生的绊脚石,没有之一。好在这一切都在 0.12 这个版本解决了,用户可以基于 Step +构成的有向无环图来编译自己的项目,不需要再折腾 CMake、Makefile、Vcpkg、Git submodule 等工具,所有的依赖使用 zon 来管理即可。 +读者如果对 Zig 构建系统还不熟悉,可以参考: +- 官方文档:[Zig Build System](https://ziglang.org/learn/build-system/) +- Zig 升级: [构建系统](https://course.ziglang.cc/engineering/build-system) + +期待一年后 Zig 的生态! +# [观点/教程]($section.id('观点/教程')) +- [Zig 中任意精度整数用途与实现](https://github.com/zigcc/forum/issues/112) :: 由于 CPU 在访问内存时,一般都会有对齐的要求,对于这种非常规的数字,在内存中的地址会是怎样的呢?可以做一个简单的实验: + + ```zig +const std = @import("std"); + +const Foo = packed struct { + a: u3, + b: u2, +}; + +pub fn main() !void { + const vs = [_]u3{ 1, 2, 3 }; + for (&vs) |*b| { + std.debug.print("{p}-{b}\n", .{ b, b.* }); + } + + std.debug.print("U3 size: {d}\n", .{@sizeOf(u3)}); + std.debug.print("Foo size: {d}\n", .{@sizeOf(Foo)}); + + const foos = [_]Foo{ + .{ .a ` 1, .b ` 3 }, + }; + + std.debug.print("foo as bytes: {b}\n", .{std.mem.sliceAsBytes(&foos)}); + + for (foos) |b| { + std.debug.print("{any}-{any}\n", .{ &b.a, &b.b }); + } +} + ``` + 输出: + ```bash +u3@104d11a2c-1 +u3@104d11a2d-10 +u3@104d11a2e-11 +U3 size: 1 +Foo size: 1 +foo as bytes: { 11001 } +u3@16b196367-u2@16b196367 + ``` + + 通过前三个输出可以知道,每个 u3 实际占用一个字节,但当用在 packed 结构中,就会变成 3 个 bit。其中的 11001 就是字段 a b 混合后的值,且 a 是三位,b 是高两位。 +- [Learnings From Building a DB in Zig](https://procmarco.com/blog/learnings-from-building-a-db-in-zig/) :: 作者分享了在一次 3 天的 Hackthon 中,使用 Zig 开发一个数据库的经历。 +- [build.zig.zon dependency hashes](https://zig.news/michalsieron/buildzigzon-dependency-hashes-47kj) :: 讲解了 zon 中依赖的 hash 是怎么计算出来的 +- [play with new comptime var rule of zig 0.12.0](https://zig.news/liyu1981/play-with-new-comptime-var-rule-of-zig-0120-333k) :: +- [To SIMD and beyond: Optimizing a simple comparison routine](https://zig.news/inspectorboat/to-simd-and-beyond-optimizing-a-simple-comparison-routine-1jkf) :: 作者在这里循序渐进的介绍了几种数字比较的技巧,从基本的方案,到 Vector,到最后利用 bit 的特点,来逐步优化,并用 godbolt 查看生成的汇编代码,是一篇不错的文章。 +- [Documentation takes another step backwards : r/Zig](https://www.reddit.com/r/Zig/comments/1cc1x2v/documentation_takes_another_step_backwards/) :: 一个 Reddit 用户对文档的抱怨 +# [项目/工具]($section.id('项目/工具')) +- [rofrol/zig-companies](https://github.com/rofrol/zig-companies) :: A list of companies using Zig in production. +- [akarpovskii/tuile](https://github.com/akarpovskii/tuile) :: A Text UI library for Zig +- [mntnmntn/zenith](https://codeberg.org/mntnmntn/zenith) :: A very minimal text editor in Zig,支持 0.12.0 版本 +- [chung-leong/zigar](https://github.com/chung-leong/zigar) :: Enable the use of Zig code in JavaScript project +- [jnordwick/zig-string](https://github.com/jnordwick/zig-string) :: Zig string library that includes small string optimization on the stack +- [FalsePattern/ZigBrains](https://github.com/FalsePattern/ZigBrains) :: Yet another zig language plugin for intellij + +# [Zig 语言更新]($section.id('zig-update')) +[2024-04-01..2024-05-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-04-01..2024-05-01) diff --git a/content/monthly/202405.org b/content/monthly/202405.org deleted file mode 100644 index d8749a9..0000000 --- a/content/monthly/202405.org +++ /dev/null @@ -1,63 +0,0 @@ -#+TITLE: 202405 -#+DATE: 2024-06-02T22:18:59+0800 -#+LASTMOD: 2024-06-04T19:26:01+0800 - -* 观点/教程 -** [[https://arne.me/blog/thoughts-on-zig][Thoughts on Zig]] -又一篇 Zig 初学者的使用体验文档,如果你也在犹豫要不要学 Zig,这是个不错的经验参考。 -** [[https://www.reddit.com/r/Zig/comments/1ckstjv/im_sold_on_zigs_simplicity/][I'm sold on Zig's simplicity : r/Zig]] -一个具有资深经验开发者,在这里描述了自己选择业余项目语言的经历: - - Rust 越来越复杂,有种发展成 C++ 的趋势 - - C++ 新版本的特性(比如 module)LSP 支持的不够好,而且历史包袱严重 - - C 缺少元编程,并且没有命名空间 - - 最后从 Andrew 的一个播客了解到 Zig,经过自己尝试,发现了 Zig 没有辜负他的期望,尽管是第一次写 Zig,但基本上没有什么难度, - 每次遇到问题,仔细想几分钟就差不多有答案了。下面是他罗列的 Zig 的一些优势: - - 十分简洁,import 返回的是一个 struct,和其他变量一样使用 - - 与 C 无缝交换, - - 具有 Result 效果的错误处理 - - 唯一缺失的就是『接口』,但这一点并不是很关键,就像在 C里也没有,但是 C 可以做任何事 -* [[https://andrewkelley.me/post/zig-new-cli-progress-bar-explained.html][Zig's New CLI Progress Bar Explained]] -Andrew 的一篇文章,讲述了在最新版的 Zig 中,对进度条的改进实现,现在的进度展示更加友好。 - -实现的难点在于在多线程环境下,如何保证高性能,文章中大致讲述了其实现: -- 首先通过预先分配好需要使用的结构,保证后续无需在进行 heap 申请 -- 通过 atomic 操作来实现一个无锁的 freelist,用于申请、释放 Node -* [[https://www.openmymind.net/Writing-a-Task-Scheduler-in-Zig/][Writing a task scheduler in Zig]] -Openmymind 作者的又一力作,通过编写一个任务调度器,讲述了多线程编程的基本要领: -- 共享的数据要加锁 -- 条件变量要和锁一起使用,会有[[https://en.wikipedia.org/wiki/Spurious_wakeup][虚假唤醒]]的问题,因此在被唤醒时,需要重新检查状态是否正确。 - #+begin_src zig -fn run(self: *Self) void { - while (true) { - self.mutex.lock(); - while (self.queue.peek() == null) { - self.cond.wait(&self.mutex); - } - // TODO - } -} - #+end_src - 它会在 wait 前释放锁,在 wait 返回时先加锁,类似下面的实现: - #+begin_src zig -fn wait(c: *std.Thread.Condition, mutex: *std.Thread.Mutex) void { - // do some setup - // ... - - mutex.unlock(); - - // whatever happens, we'll always return with this locked - defer mutex.lock(); - - // wait for signal - // or timeout if calling timedWait - // ... -} - #+end_src -* 项目/工具 -- [[https://github.com/chung-leong/zigar][zigar]] :: Enable the use of Zig code in JavaScript project。它可以让你直接在 JS 中调用 zig 代码,背后原理是编译成了 wasm 实现的。 -- [[https://github.com/srijan-paul/nez][srijan-paul/nez]] :: An emulator for the NES console. -- [[https://github.com/deckarep/ziglang-set][deckarep/ziglang-set]] :: A generic and general purpose Set implementation for the Zig language -- [[https://github.com/akarpovskii/tuile][akarpovskii/tuile]] :: A Text UI library for Zig - -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-05-01..2024-06-01][Zig 语言更新]] diff --git a/content/monthly/202405.smd b/content/monthly/202405.smd new file mode 100644 index 0000000..5d9adc5 --- /dev/null +++ b/content/monthly/202405.smd @@ -0,0 +1,68 @@ +--- +.title = "202405", +.date = @date("2024-06-02T22:18:59+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2024-06-04T19:26:01+0800" }, +--- + +# [观点/教程]($section.id('观点/教程')) +## [[Thoughts on Zig](https://arne.me/blog/thoughts-on-zig)]($section.id('[Thoughts on Zig](https://arne.me/blog/thoughts-on-zig)')) +又一篇 Zig 初学者的使用体验文档,如果你也在犹豫要不要学 Zig,这是个不错的经验参考。 +## [[I'm sold on Zig's simplicity : r/Zig](https://www.reddit.com/r/Zig/comments/1ckstjv/im_sold_on_zigs_simplicity/)]($section.id('[I\'m sold on Zig\'s simplicity : r/Zig](https://www.reddit.com/r/Zig/comments/1ckstjv/im_sold_on_zigs_simplicity/)')) +一个具有资深经验开发者,在这里描述了自己选择业余项目语言的经历: + - Rust 越来越复杂,有种发展成 C++ 的趋势 + - C++ 新版本的特性(比如 module)LSP 支持的不够好,而且历史包袱严重 + - C 缺少元编程,并且没有命名空间 + + 最后从 Andrew 的一个播客了解到 Zig,经过自己尝试,发现了 Zig 没有辜负他的期望,尽管是第一次写 Zig,但基本上没有什么难度, + 每次遇到问题,仔细想几分钟就差不多有答案了。下面是他罗列的 Zig 的一些优势: + - 十分简洁,import 返回的是一个 struct,和其他变量一样使用 + - 与 C 无缝交换, + - 具有 Result 效果的错误处理 + - 唯一缺失的就是『接口』,但这一点并不是很关键,就像在 C里也没有,但是 C 可以做任何事 +# [[Zig's New CLI Progress Bar Explained](https://andrewkelley.me/post/zig-new-cli-progress-bar-explained.html)]($section.id('[Zig\'s New CLI Progress Bar Explained](https://andrewkelley.me/post/zig-new-cli-progress-bar-explained.html)')) +Andrew 的一篇文章,讲述了在最新版的 Zig 中,对进度条的改进实现,现在的进度展示更加友好。 + +实现的难点在于在多线程环境下,如何保证高性能,文章中大致讲述了其实现: +- 首先通过预先分配好需要使用的结构,保证后续无需在进行 heap 申请 +- 通过 atomic 操作来实现一个无锁的 freelist,用于申请、释放 Node +# [[Writing a task scheduler in Zig](https://www.openmymind.net/Writing-a-Task-Scheduler-in-Zig/)]($section.id('[Writing a task scheduler in Zig](https://www.openmymind.net/Writing-a-Task-Scheduler-in-Zig/)')) +Openmymind 作者的又一力作,通过编写一个任务调度器,讲述了多线程编程的基本要领: +- 共享的数据要加锁 +- 条件变量要和锁一起使用,会有[虚假唤醒](https://en.wikipedia.org/wiki/Spurious_wakeup)的问题,因此在被唤醒时,需要重新检查状态是否正确。 + ```zig +fn run(self: *Self) void { + while (true) { + self.mutex.lock(); + while (self.queue.peek() == null) { + self.cond.wait(&self.mutex); + } + // TODO + } +} + ``` + 它会在 wait 前释放锁,在 wait 返回时先加锁,类似下面的实现: + ```zig +fn wait(c: *std.Thread.Condition, mutex: *std.Thread.Mutex) void { + // do some setup + // ... + + mutex.unlock(); + + // whatever happens, we'll always return with this locked + defer mutex.lock(); + + // wait for signal + // or timeout if calling timedWait + // ... +} + ``` +# [项目/工具]($section.id('项目/工具')) +- [zigar](https://github.com/chung-leong/zigar) :: Enable the use of Zig code in JavaScript project。它可以让你直接在 JS 中调用 zig 代码,背后原理是编译成了 wasm 实现的。 +- [srijan-paul/nez](https://github.com/srijan-paul/nez) :: An emulator for the NES console. +- [deckarep/ziglang-set](https://github.com/deckarep/ziglang-set) :: A generic and general purpose Set implementation for the Zig language +- [akarpovskii/tuile](https://github.com/akarpovskii/tuile) :: A Text UI library for Zig + +# [[Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2024-05-01..2024-06-01)]($section.id('[Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2024-05-01..2024-06-01)')) diff --git a/content/monthly/202406.org b/content/monthly/202406.smd similarity index 65% rename from content/monthly/202406.org rename to content/monthly/202406.smd index 806c67e..fef4f7a 100644 --- a/content/monthly/202406.org +++ b/content/monthly/202406.smd @@ -1,27 +1,34 @@ -#+TITLE: 202406 | 0.13 来了 -#+DATE: 2024-07-01T20:34:51+0800 -#+LASTMOD: 2024-07-01T21:51:32+0800 -* 重大事件 +--- +.title = "202406 | 0.13 来了", +.date = @date("2024-07-01T20:34:51+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2024-07-01T21:51:32+0800" }, +--- + +# [重大事件]($section.id('重大事件')) 2024-06-07,0.13.0 发布,历时不足 2 个月,有 73 位贡献者,一共进行了 415 次提交! -这是一个相对较短的发布周期,主要原因是工具链升级,例如升级到 [[https://ziglang.org/download/0.13.0/release-notes.html#LLVM-18][LLVM 18]]。 +这是一个相对较短的发布周期,主要原因是工具链升级,例如升级到 [LLVM 18](https://ziglang.org/download/0.13.0/release-notes.html#LLVM-18)。 -一个比较大的 Breaking changes 是 =ComptimeStringMap= 被重命名为了 =StaticStringMap= , -使用方式也发生了变化,更多细节可参考:[[https://github.com/ziglang/zig/pull/19682][#19682]] -#+begin_src zig +一个比较大的 Breaking changes 是 `ComptimeStringMap` 被重命名为了 `StaticStringMap` , +使用方式也发生了变化,更多细节可参考:[#19682](https://github.com/ziglang/zig/pull/19682) +```zig const map = std.StaticStringMap(T).initComptime(kvs_list); -#+end_src +``` 0.14.0 发布周期的主题将是编译速度。将在 0.14.0 发布周期中努力实现一些即将到来的里程碑: - 使 x86 后端成为调试模式的默认后端。 -- COFF 的链接器支持。消除对 LLVM [[https://lld.llvm.org/][LLD]] 的依赖。 +- COFF 的链接器支持。消除对 LLVM [LLD](https://lld.llvm.org/) 的依赖。 - 启用增量编译以实现快速重建。 - 将并发引入语义分析,进一步提高编译速度。 -* 观点/教程 -** [[https://www.openmymind.net/Leveraging-Zigs-Allocators/][Leveraging Zig's Allocators]] +# [观点/教程]($section.id('观点/教程')) +## [[Leveraging Zig's Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/)]($section.id('[Leveraging Zig\'s Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/)')) 老朋友 openmymind 的又一篇好文章:如何利用 Zig 的 Allocator 来实现请求级别的内存分配。 -Zig Allocator 的最佳应用。[[/post/2024/06/16/leveraging-zig-allocator/][这里]]它的中文翻译。 -#+begin_src zig +// TODO +Zig Allocator 的最佳应用。这里(/post/2024/06/16/leveraging-zig-allocator/)它的中文翻译。 +```zig const FallbackAllocator = struct { primary: Allocator, fallback: Allocator, @@ -30,7 +37,7 @@ const FallbackAllocator = struct { pub fn allocator(self: *FallbackAllocator) Allocator { return .{ .ptr = self, - .vtable = &.{.alloc = alloc, .resize = resize, .free = free}, + .vtable ` &.{.alloc ` alloc, .resize ` resize, .free ` free}, }; } @@ -82,8 +89,8 @@ fn run(worker: *Worker) void { worker.write(conn.res); } } -#+end_src -** [[https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/][On Zig vs Rust at work and the choice we made]] +``` +## [[On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/)]($section.id('[On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/)')) - https://news.ycombinator.com/item?id=40735667 这篇文章作者描述了所在公司在改造老 C/C++ 项目时,为什么选择了 Zig 而不是 Rust。 重写的项目运行在多个平台上(Web、移动端、VR 设备),因此最靠谱的方案就是暴露一个 C API,然后通过 FFI 来调用。在做决策时,重点关注以下两点: @@ -100,13 +107,13 @@ fn run(worker: *Worker) void { | C 交互性 | 生态丰富 | 编译器本身就是 C 编译器,这样就可以逐步重写项目 | 如果只是根据上面的比较,貌似还看不出选择 Zig 的动机,因此作者在最后提到: -#+begin_quote +> Zig 大大减少了移植现有代码库和确保所有平台兼容性所需的时间和精力。我们的团队无法相信 Rust 能让这一切变得如此简单。 -#+end_quote + 相信这也是大部分人选择 Zig 的原因:简洁、高效。 -** [[https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/][Packing some Zig before going for the countryside]] -作者列举的一些 Zig 学习资料、常用类库。该作者的另一篇文章也有不少资料:[[https://ludwigabap.bearblog.dev/2024-collection-of-zig-resources/][2024 Collection of Zig resources]] -** [[https://turso.tech/blog/why-i-am-not-yet-ready-to-switch-to-zig-from-rust][Why I am not yet ready to switch to Zig from Rust]] +## [[Packing some Zig before going for the countryside](https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/)]($section.id('[Packing some Zig before going for the countryside](https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/)')) +作者列举的一些 Zig 学习资料、常用类库。该作者的另一篇文章也有不少资料:[2024 Collection of Zig resources](https://ludwigabap.bearblog.dev/2024-collection-of-zig-resources/) +## [[Why I am not yet ready to switch to Zig from Rust](https://turso.tech/blog/why-i-am-not-yet-ready-to-switch-to-zig-from-rust)]($section.id('[Why I am not yet ready to switch to Zig from Rust](https://turso.tech/blog/why-i-am-not-yet-ready-to-switch-to-zig-from-rust)')) Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较喜欢 C,但 C 不是一门安全的语言,因此通过 Rust,作者可以避免 写出 SIGSEGVS 的代码,尽管 Rust 是门复杂的语言,但是因为它有完善的生态(有大公司如微软、谷歌等做背书)、已经内存安全等特点, 已经是作者系统编程的首选。 @@ -117,10 +124,10 @@ Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较 但是笔者有一点不能理解,就是该作者觉得 comptime 不好用,相比之下,他更喜欢 C 里面的宏。comptime 就是为了 C 宏的不足 而诞生的,社区普遍也觉得 comptime 是个新颖的设计,笔者也是第一次见到这个观点,只能说,萝卜青菜,各有所爱。 -其他社区的一些讨论:[[https://lobste.rs/s/0mnhdx][Lobsters]]、[[https://news.ycombinator.com/item?id=40681862][Hacker News]] -* 项目/工具 -- [[https://github.com/malcolmstill/zware][malcolmstill/zware]] :: Zig WebAssembly Runtime Engine -- [[https://github.com/Cloudef/zig-aio][Cloudef/zig-aio]] :: io_uring like asynchronous API and coroutine powered IO tasks for zig - +其他社区的一些讨论:[Lobsters](https://lobste.rs/s/0mnhdx)、[Hacker News](https://news.ycombinator.com/item?id=40681862) +# [项目/工具]($section.id('项目/工具')) +- [malcolmstill/zware](https://github.com/malcolmstill/zware) :: Zig WebAssembly Runtime Engine +- [Cloudef/zig-aio](https://github.com/Cloudef/zig-aio) :: io_uring like asynchronous API and coroutine powered IO tasks for zig -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-06-01..2024-07-01][Zig 语言更新]] +# [Zig 语言更新]($section.id('zig-update')) +[2024-06-01..2024-07-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-06-01..2024-07-01) diff --git a/content/monthly/202407.org b/content/monthly/202407.smd similarity index 62% rename from content/monthly/202407.org rename to content/monthly/202407.smd index efca3d5..e6c6f6b 100644 --- a/content/monthly/202407.org +++ b/content/monthly/202407.smd @@ -1,17 +1,21 @@ -#+TITLE: 202407 | Zig 成为最热门的编程语言 -#+DATE: 2024-07-05T21:22:52+0800 -#+LASTMOD: 2024-08-01T07:18:08+0800 +--- +.title = "202407 | Zig 成为最热门的编程语言", +.date = @date("2024-07-05T21:22:52+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2024-08-01T07:18:08+0800" }, +--- -* 重大事件 -在[[https://leaddev.com/tech/why-zig-one-hottest-programming-languages-learn][这篇文章]]里,作者引用 Stackoverflow 2024 年的[[https://survey.stackoverflow.co/2024/technology][调查报告]],指出 Zig 语言是最热门的编程语言之一,并且 Zig 开发者的薪水都很高,平均年收入为75,332美元! +# [重大事件]($section.id('重大事件')) +在[这篇文章](https://leaddev.com/tech/why-zig-one-hottest-programming-languages-learn)里,作者引用 Stackoverflow 2024 年的[调查报告](https://survey.stackoverflow.co/2024/technology),指出 Zig 语言是最热门的编程语言之一,并且 Zig 开发者的薪水都很高,平均年收入为75,332美元! -{{< figure src="/images/stackoverflow2024-highly-desired.webp" caption="Zig 受欢迎程度">}} +{{< figure src`"/images/stackoverflow2024-highly-desired.webp" caption`"Zig 受欢迎程度">}} -{{< figure src="/images/stackoverflow2024-salary.webp" caption="Zig 薪水对比">}} +{{< figure src`"/images/stackoverflow2024-salary.webp" caption`"Zig 薪水对比">}} 尽管使用 Zig 语言的开发者仅占调查人数的 1%,但上升趋势明显。Zig 语言的倡导者、自由和开放源码软件开发者 Ali Cheragi 说: -#+begin_quote +> Zig 的魅力在于它的简洁性、现代设计以及在底层控制和运行时安全性之间取得的平衡。 -#+end_quote Zig 开发者的一些观点: - 我选择 Zig 作为我的日常用语,是因为它独特的功能和目标组合。我被 Zig 的安全性所吸引,因为它可以让我控制最底层的部件。 @@ -19,18 +23,18 @@ Zig 开发者的一些观点: - Zig 正在对大量编程基础架构进行彻底改造,而这些基础架构在过去 40 年里无人敢碰。 C 和 C++ 是著名的核心编程语言,在这两种语言中,你可以完全控制硬件。 但与此同时,这些语言的工具链却非常糟糕。 Zig 允许用户涉猎这些核心编程语言,但可以使用更好的工具链,兼容各种语言和更丰富的功能。 -* 观点/教程 -** [[https://kristoff.it/blog/improving-your-zls-experience/][Improving Your Zig Language Server Experience]] +# [观点/教程]($section.id('观点/教程')) +## [[Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/)]($section.id('[Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/)')) Loris Cro 的最新文章,介绍了一个改进 Zig 编码体验的小技巧,十分推荐大家使用。具体来说是这样的: 通过配置 zls,达到保存文件时,自动进行源码检查,而且速度非常快! - #+begin_src js + ```js { "enable_build_on_save": true, "build_on_save_step": "check" } - #+end_src - 将上述内存保存到 zls 的配置文件中,(路径可以通过 =zls --show-config-path= 查看 ),zls 就会在保存时,自动执行 =zig build check= ,这个 =check= 一般来说是这样的: - #+begin_src zig + ``` + 将上述内存保存到 zls 的配置文件中,(路径可以通过 `zls --show-config-path` 查看 ),zls 就会在保存时,自动执行 `zig build check` ,这个 `check` 一般来说是这样的: + ```zig const exe_check = b.addExecutable(.{ .name = "foo", .root_source_file = b.path("src/main.zig"), @@ -40,26 +44,26 @@ const exe_check = b.addExecutable(.{ const check = b.step("check", "Check if foo compiles"); check.dependOn(&exe_check.step); - #+end_src + ``` -由于 Zig 目前的一个 bug([[https://github.com/ziglang/zig/issues/18877][#18877]]),这个 =exe_check= 不能作为 install、run 的依赖,否则在编译时,就不会增加 =-fno-emit-bin= 选项。 +由于 Zig 目前的一个 bug([#18877](https://github.com/ziglang/zig/issues/18877)),这个 `exe_check` 不能作为 install、run 的依赖,否则在编译时,就不会增加 `-fno-emit-bin` 选项。 而这个选项的作用就是让 Zig 来分析我们的代码,但是不会调用 LLVM 来生成最终的二进制文件,因此速度会比较快。 -这个配置有个缺点,就是它是个全局配置,在 [[https://github.com/zigtools/zls/issues/1687#issuecomment-1953202544][zigtools/zls#1687]] 有讨论如何改成项目级别的,本质上就是定制 zls 的启动参数。 -#+begin_src bash +这个配置有个缺点,就是它是个全局配置,在 [zigtools/zls#1687](https://github.com/zigtools/zls/issues/1687#issuecomment-1953202544) 有讨论如何改成项目级别的,本质上就是定制 zls 的启动参数。 +```bash zls --config-path zls.json -#+end_src +``` 这样不同的项目就可以用不同的检查步骤了。 -** [[https://guergabo.substack.com/p/systems-distributed-24][Systems Distributed '24]] +## [[Systems Distributed '24](https://guergabo.substack.com/p/systems-distributed-24)]($section.id('[Systems Distributed \'24](https://guergabo.substack.com/p/systems-distributed-24)')) 作者对这次会议的一个回顾总结,议题主要有如下几个方向: - Systems Thinking and Engineering Culture - The Rise of New Software Abstractions - Ensuring Safe and Correct Software - Lessons from Building Distributed Databases - Notes from Water Cooler Chats -** [[https://jstrieb.github.io/posts/c-reflection-zig/][C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself]] +## [[C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself](https://jstrieb.github.io/posts/c-reflection-zig/)]($section.id('[C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself](https://jstrieb.github.io/posts/c-reflection-zig/)')) 该作者分享了利用 typeInfo 来在编译时获取字段名的能力,要知道,在 C 里面是没有这个功能的。 -#+begin_src zig +```zig pub export fn WindowProc(hwnd: win32.HWND, uMsg: c_uint, wParam: win32.WPARAM, lParam: win32.LPARAM) callconv(windows.WINAPI) win32.LRESULT { // Handle each type of window message we care about _ = switch (uMsg) { @@ -72,9 +76,9 @@ pub export fn WindowProc(hwnd: win32.HWND, uMsg: c_uint, wParam: win32.WPARAM, l return win32.DefWindowProcA(hwnd, uMsg, wParam, lParam); } -#+end_src -上面这个函数是 Window 编写窗口应用时用到的回调函数,Window 操作系统会把用户触发的事件通过 =uMsg= 传递过来,为了能够从一个数字,找对对应的名字,在 Zig 里面可以用如下函数实现: -#+begin_src zig +``` +上面这个函数是 Window 编写窗口应用时用到的回调函数,Window 操作系统会把用户触发的事件通过 `uMsg` 传递过来,为了能够从一个数字,找对对应的名字,在 Zig 里面可以用如下函数实现: +```zig // The WM_* macros have values less than 65536, so an array of that size can // represent all of them fn get_window_messages() [65536][:0]const u8 { @@ -90,8 +94,8 @@ fn get_window_messages() [65536][:0]const u8 { // We return by value here, not by reference, so this is safe to do return result; } -#+end_src -** [[https://effectivetypescript.com/2024/07/17/advent2023-zig/][A TypeScripter's Take on Zig (Advent of Code 2023)]] +``` +## [[A TypeScripter's Take on Zig (Advent of Code 2023)](https://effectivetypescript.com/2024/07/17/advent2023-zig/)]($section.id('[A TypeScripter\'s Take on Zig (Advent of Code 2023)](https://effectivetypescript.com/2024/07/17/advent2023-zig/)')) 以下该作者的一些心得体会: - Zig 没有 scanf 等价物,正则表达式也不方便。因此,对于解析输入,它是拆分、拆分、拆分。最后,我分解出了一些 splitIntoBuf 和提取 IntsIntoBuf 帮助程序,这些帮助程序可以很快地读取大多数问题的输入。 @@ -101,20 +105,20 @@ fn get_window_messages() [65536][:0]const u8 { - 尽量避免将字符串复制到 StringHashMap 中用作键。从 JS 发出这样的命令感觉很自然,但是在 Zig 中会很尴 尬,因为您需要跟踪这些字符串以便稍后释放它们。如果您可以将您的键放入一个结构或元组中,那将会工作得 更好,因为它们具有值语义。如果需要字符串,可以使用切片。 -- 注意数值范围的错误。如果你想包含 max,它是 =min..(max + 1)= ,而不是 =min..max= 。 +- 注意数值范围的错误。如果你想包含 max,它是 `min..(max + 1)` ,而不是 `min..max` 。 - 代码中将有大量的@intCast。 -- 我发现奇怪的是 Zig 有一个内置的 PriorityQueue,但是没有内置的 Queue,可以用 =std.SinglyLinkedList= 替代 +- 我发现奇怪的是 Zig 有一个内置的 PriorityQueue,但是没有内置的 Queue,可以用 `std.SinglyLinkedList` 替代 - 用于处理字符串的许多函数都在 std.mem 中,例如 std.mem.eql 和 std.mem.startsWith - 使用 std.met.eql 比较 structs,而不是 ~=~ -- 有一个按偏移量和长度切片的技巧: =array [start..][0..length]= +- 有一个按偏移量和长度切片的技巧: `array [start..][0..length]` - 记忆函数通常是很有用的。我不知道 Zig 有没有通用的方法 - 调试构建比优化构建慢得多,有时候慢10倍。如果你在一个合理的时间内得到一个答案的10倍之内,尝试一个不同的发布模式。 - 迭代时不要对数组列表进行修改 -- 在 JavaScript 允许您内联表达式的某些情况下,您可能需要分解出一个变量来澄清生存期。看看[[https://github.com/ziglang/zig/issues/12414][这个问题]]。 +- 在 JavaScript 允许您内联表达式的某些情况下,您可能需要分解出一个变量来澄清生存期。看看[这个问题](https://github.com/ziglang/zig/issues/12414)。 +# [项目/工具]($section.id('项目/工具')) +- [18alantom/fex](https://github.com/18alantom/fex) :: A command-line file explorer prioritizing quick navigation. +- [griush/zm](https://github.com/griush/zm) :: SIMD Math library fully cross-platform -* 项目/工具 -- [[https://github.com/18alantom/fex][18alantom/fex]] :: A command-line file explorer prioritizing quick navigation. -- [[https://github.com/griush/zm][griush/zm]] :: SIMD Math library fully cross-platform - -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-07-01..2024-08-01][Zig 语言更新]] +# [Zig 语言更新]($section.id('zig-update')) +[2024-07-01..2024-08-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-07-01..2024-08-01) diff --git a/content/monthly/202410.org b/content/monthly/202410.org deleted file mode 100644 index 128a4f2..0000000 --- a/content/monthly/202410.org +++ /dev/null @@ -1,68 +0,0 @@ -#+TITLE: 202410 | 向 Zig 软件基金会认捐 30 万美元 -#+DATE: 2024-10-26T00:17:35+0800 -#+LASTMOD: 2024-11-03T15:21:14+0800 -* 重大事件 -** 向 Zig 软件基金会认捐 30 万美元 -Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金会 (ZSF) 捐赠了 300,000 美元。 -#+begin_quote -两年内每年分期支付15万美元。第一期已经转账。 -#+end_quote - -我从 2019 年的某个时候开始关注 Zig 项目。 我在 2021 年公开分享了我对该项目的兴奋之情。 同年晚些时候,我开始使用 Zig,到 2022 年初,我开始撰写关于 Zig 的文章,并为编译器做出贡献。 2023 年,我公开分享了用 Zig 编写的终端项目 Ghostty。 - -如今,我大部分的编码时间都花在了 Zig 上。 我的家人喜欢支持我们相信的事业2。 作为其中的一部分,我们希望支持那些我们认为可以带来变革和影响的独立软件项目,这既是回馈给我如此之多的社区的一种方式,更重要的是,这也是彰显和鼓励为热爱而构建的文化的一种方式。 Zig 就是这样一个项目。 -* 观点/教程 -** [[https://mrcat.au/blog/zig_is_cool/][Zig is everything I want C to be]] -对 Zig 的特色进行了简单扼要的介绍,主要有: -1. UB 行为检测。 - - Zig 的指针不能是 null,需要用 optional 类型 - - C 里面的 =void*= 等价于 Zig 里面的 =?*anyopaque= 。 =void= 在 C 里面有两个意思,第一是『什么都没有』,第二是『类型不确定』,但 =void= 在 Zig 中只有第一个含义,因此用了 =anyopaque= 来表示类型擦除的指针(type-erased pointers)。 - - 数组越界检查 - - 整数溢出 -2. Bitfield, =packed struct= 可以方便的用来进行协议解析,比如对于 32 位的 RISC-V 的指令,可以这么定义解析: - #+begin_src zig -const IType = packed struct { - opcode: u7, - rd: u5, - funct3: u3, - rs1: u5, - imm: i12, // For sign-extension -}; - -const encoded_instr: u32 = 0xFFF34293; -const instr: IType = @bitCast(encoded_instr); - #+end_src -3. comptime,Zig 进行元编程的基础,类型是一等成员 -4. 与 C 无缝交互, =zig cc= 是交叉编译的首选 -** [[https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html][Building Nintendo 3DS Homebrew with Zig]] -** [[https://kristoff.it/blog/critical-social-infrastructure/][Critical Social Infrastructure for Zig Communities | Loris Cro's Blog]] -对于一个试图共同学习如何制作大家都喜欢的软件的社区来说,能够分享想法并开展合作至关重要,但社交平台的不断起伏会导致连接中断,这对于一个从一开始就希望去中心化的社区来说是个大问题。 - -我有这种想法已经有一段时间了,但随着时间的推移,我们似乎越来越清楚地认识到,我们需要投资于能够长期保持可靠的交流形式,在这种交流形式中,变化是一种信号,表明社区正在发生转变(因此需要一种新的网络形态),而不是表明所选择的社交平台即将被收购/上市/加入人工智能大战。 - -开发者日志:迈向可靠社会基础设施的第一步 -- https://ziglang.org/devlog/ -- https://zine-ssg.io/log/ -** [[https://ziglang.org/news/website-zine/][The Zig Website Has Been Re-engineered]] -Zig 官网已经用 [[https://zine-ssg.io/][Zine]] 重写! -** [[https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/][Rust vs. Zig in Reality: A (Somewhat) Friendly Debate]] -** [[https://kevinlynagh.com/rust-zig/][Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun]] -** [[https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html][Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig]] -** 视频 -*** [[https://www.youtube.com/watch?v=xOySJpQlmv4&feature=youtu.be][I made an operating system that self replicates doom on a network “from scratch”]] -*** [[https://www.youtube.com/watch?feature=shared&v=3fWx5BOiUiY][Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)]] -*** [[https://www.youtube.com/live/Kf7BIPUUfsc?t=764][Let's explore Vulkan API with Zig programming language from scratch]] -* 项目/工具 -- [[https://github.com/laohanlinux/boltdb-zig][laohanlinux/boltdb-zig]] :: a zig implement kv database -- [[https://github.com/E-xyza/zigler][zigler]] :: Zig NIFs in Elixir -- [[https://github.com/gdonald/blackjack-zig][gdonald/blackjack-zig]] :: Console Blackjack written in Zig -- [[https://github.com/rabinnh/zig-vscode-linux][rabinnh/zig-vscode-linux]] :: Instructions on setting up VSCode to debug Zig on Linux -- [[https://github.com/lframosferreira/brainzuck][lframosferreira/brainzuck]] :: [[https://en.wikipedia.org/wiki/Brainfuck][Brainf*ck]] interpreter written in Zig 0.12.0! Have fun! -- [[https://github.com/BitlyTwiser/snek][BitlyTwiser/snek]] :: A simple CLI parser to build CLI applications in Zig -- [[https://github.com/zml/zml][zml/zml]] :: High performance AI inference stack. Built for production. -- [[https://github.com/BitlyTwiser/zdotenv][BitlyTwiser/zdotenv]] :: A port of Godotenv for Zig -- [[https://github.com/sbancuz/OpenMP-zig][sbancuz/OpenMP-zig]] :: An implementation of the OpenMP directives for Zig -- [[https://github.com/tusharsadhwani/zigimports][tusharsadhwani/zigimports]] :: Automatically remove unused imports and globals from Zig files. -- [[https://github.com/Mario-SO/zigitor][Mario-SO/zigitor]] :: Video editor 🎬 written in Zig ⚡ using raylib -- [[https://github.com/pwbh/ymlz][pwbh/ymlz]] :: Small and convenient yaml parser for Zig -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-10-01..2024-11-01][Zig 语言更新]] diff --git a/content/monthly/202410.smd b/content/monthly/202410.smd new file mode 100644 index 0000000..a83d747 --- /dev/null +++ b/content/monthly/202410.smd @@ -0,0 +1,74 @@ +--- +.title = "202410 | 向 Zig 软件基金会认捐 30 万美元", +.date = @date("2024-10-26T00:17:35+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2024-11-03T15:21:14+0800" }, +--- + +# [重大事件]($section.id('重大事件')) +## [向 Zig 软件基金会认捐 30 万美元]($section.id('向 Zig 软件基金会认捐 30 万美元')) +Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金会 (ZSF) 捐赠了 300,000 美元。 +> +两年内每年分期支付15万美元。第一期已经转账。 + +我从 2019 年的某个时候开始关注 Zig 项目。 我在 2021 年公开分享了我对该项目的兴奋之情。 同年晚些时候,我开始使用 Zig,到 2022 年初,我开始撰写关于 Zig 的文章,并为编译器做出贡献。 2023 年,我公开分享了用 Zig 编写的终端项目 Ghostty。 + +如今,我大部分的编码时间都花在了 Zig 上。 我的家人喜欢支持我们相信的事业2。 作为其中的一部分,我们希望支持那些我们认为可以带来变革和影响的独立软件项目,这既是回馈给我如此之多的社区的一种方式,更重要的是,这也是彰显和鼓励为热爱而构建的文化的一种方式。 Zig 就是这样一个项目。 +# [观点/教程]($section.id('观点/教程')) +## [[Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/)]($section.id('[Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/)')) +对 Zig 的特色进行了简单扼要的介绍,主要有: +1. UB 行为检测。 + - Zig 的指针不能是 null,需要用 optional 类型 + - C 里面的 `void*` 等价于 Zig 里面的 `?*anyopaque` 。 `void` 在 C 里面有两个意思,第一是『什么都没有』,第二是『类型不确定』,但 `void` 在 Zig 中只有第一个含义,因此用了 `anyopaque` 来表示类型擦除的指针(type-erased pointers)。 + - 数组越界检查 + - 整数溢出 +2. Bitfield, `packed struct` 可以方便的用来进行协议解析,比如对于 32 位的 RISC-V 的指令,可以这么定义解析: + ```zig +const IType = packed struct { + opcode: u7, + rd: u5, + funct3: u3, + rs1: u5, + imm: i12, // For sign-extension +}; + +const encoded_instr: u32 = 0xFFF34293; +const instr: IType = @bitCast(encoded_instr); + ``` +3. comptime,Zig 进行元编程的基础,类型是一等成员 +4. 与 C 无缝交互, `zig cc` 是交叉编译的首选 +## [[Building Nintendo 3DS Homebrew with Zig](https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html)]($section.id('[Building Nintendo 3DS Homebrew with Zig](https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html)')) +## [[Critical Social Infrastructure for Zig Communities | Loris Cro's Blog](https://kristoff.it/blog/critical-social-infrastructure/)]($section.id('[Critical Social Infrastructure for Zig Communities | Loris Cro\'s Blog](https://kristoff.it/blog/critical-social-infrastructure/)')) +对于一个试图共同学习如何制作大家都喜欢的软件的社区来说,能够分享想法并开展合作至关重要,但社交平台的不断起伏会导致连接中断,这对于一个从一开始就希望去中心化的社区来说是个大问题。 + +我有这种想法已经有一段时间了,但随着时间的推移,我们似乎越来越清楚地认识到,我们需要投资于能够长期保持可靠的交流形式,在这种交流形式中,变化是一种信号,表明社区正在发生转变(因此需要一种新的网络形态),而不是表明所选择的社交平台即将被收购/上市/加入人工智能大战。 + +开发者日志:迈向可靠社会基础设施的第一步 +- https://ziglang.org/devlog/ +- https://zine-ssg.io/log/ +## [[The Zig Website Has Been Re-engineered](https://ziglang.org/news/website-zine/)]($section.id('[The Zig Website Has Been Re-engineered](https://ziglang.org/news/website-zine/)')) +Zig 官网已经用 [Zine](https://zine-ssg.io/) 重写! +## [[Rust vs. Zig in Reality: A (Somewhat) Friendly Debate](https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/)]($section.id('[Rust vs. Zig in Reality: A (Somewhat) Friendly Debate](https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/)')) +## [[Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun](https://kevinlynagh.com/rust-zig/)]($section.id('[Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun](https://kevinlynagh.com/rust-zig/)')) +## [[Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig](https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html)]($section.id('[Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig](https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html)')) +## [视频]($section.id('视频')) +### [[I made an operating system that self replicates doom on a network "from scratch"](https://www.youtube.com/watch?v`xOySJpQlmv4&feature`youtu.be)]($section.id('[I made an operating system that self replicates doom on a network "from scratch"](https://www.youtube.com/watch?v`xOySJpQlmv4&feature`youtu.be)')) +### [[Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature`shared&v`3fWx5BOiUiY)]($section.id('[Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature`shared&v`3fWx5BOiUiY)')) +### [[Let's explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764)]($section.id('[Let\'s explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764)')) +# [项目/工具]($section.id('项目/工具')) +- [laohanlinux/boltdb-zig](https://github.com/laohanlinux/boltdb-zig) :: a zig implement kv database +- [zigler](https://github.com/E-xyza/zigler) :: Zig NIFs in Elixir +- [gdonald/blackjack-zig](https://github.com/gdonald/blackjack-zig) :: Console Blackjack written in Zig +- [rabinnh/zig-vscode-linux](https://github.com/rabinnh/zig-vscode-linux) :: Instructions on setting up VSCode to debug Zig on Linux +- [lframosferreira/brainzuck](https://github.com/lframosferreira/brainzuck) :: [Brainf*ck](https://en.wikipedia.org/wiki/Brainfuck) interpreter written in Zig 0.12.0! Have fun! +- [BitlyTwiser/snek](https://github.com/BitlyTwiser/snek) :: A simple CLI parser to build CLI applications in Zig +- [zml/zml](https://github.com/zml/zml) :: High performance AI inference stack. Built for production. +- [BitlyTwiser/zdotenv](https://github.com/BitlyTwiser/zdotenv) :: A port of Godotenv for Zig +- [sbancuz/OpenMP-zig](https://github.com/sbancuz/OpenMP-zig) :: An implementation of the OpenMP directives for Zig +- [tusharsadhwani/zigimports](https://github.com/tusharsadhwani/zigimports) :: Automatically remove unused imports and globals from Zig files. +- [Mario-SO/zigitor](https://github.com/Mario-SO/zigitor) :: Video editor 🎬 written in Zig ⚡ using raylib +- [pwbh/ymlz](https://github.com/pwbh/ymlz) :: Small and convenient yaml parser for Zig +# [Zig 语言更新]($section.id('zig-update')) +[2024-10-01..2024-11-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-10-01..2024-11-01) diff --git a/content/monthly/202411.org b/content/monthly/202411.org deleted file mode 100644 index 53912b2..0000000 --- a/content/monthly/202411.org +++ /dev/null @@ -1,87 +0,0 @@ -#+TITLE: 202411 -#+DATE: 2024-12-09T21:02:40+0800 -#+LASTMOD: 2024-12-09T23:27:21+0800 -* 观点/教程 -** [[https://injuly.in/blog/announcing-jam/index.html][Why am I writing a JavaScript toolchain in Zig?]] -[[https://github.com/srijan-paul/jam][JAM]] 作者写的一篇文章,分析里市面上现有的 JS 工具链(bundler、formatter、linter 等),虽然已经很好用,但是不够快。下面是他举的几个例子: -- Lossless, cache efficient syntax trees,现在通用的 JS 语法树表示是 [[https://github.com/estree/estree][ESTree]],尽管设计上很简洁,但在遍历时不够高效,需要有遍历多次 - 才能得到有用信息(eslint 里就有四次!),而且都是指针的树结构非常不利用重复利用 CPU,Carbon 编译器就有一种更紧凑的 AST 表示。 -- Compile time AST query processing。Lint 的规则大部分都是模式匹配,大部分时候都有多个嵌套的 if 逻辑,为了简化插件开发者,eslint - 采用了一种 [[https://estools.github.io/esquery/][esquery]] 的语法,示例: - #+begin_src js -if ( - node.type == "CallExpression" && - node.callee.type === "MemberExpression" && - node.callee.object.type === "Identifier" && - node.callee.object.name === "child_process" -) { - // many of these checks are to satisfy typescript^ -} - - -if (matches( - 'CallExpression[callee.object.name = child_process]', - node - )) { - // much better -} - #+end_src - esquery 的问题在于执行效率,通过借助于 zig 的 comptime 来在编译器翻译 esquery 就避免了运行时开销。 -** [[https://kristoff.it/blog/advent-of-code-zig/][Advent of Code in Zig | Loris Cro's Blog]] -一年一度的 AoC 又到了,这篇文章里给出了一些使用的技巧来帮助大家用 Zig 来解决 AoC 问题。 -- 工具链,最新的版本 0.13 和 zls -- 手册,和 [[https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code][How to read the standard library source code · ziglang/zig Wiki]] -- 使用 =embedFile= 来嵌入输入的测试用例: - #+begin_src zig -const std = @import("std"); -const input = @embedFile("path/to/input.txt"); - -pub fn main() !void { - for (input) |byte| { - //... - } -} - #+end_src -- 分词 - - std.mem.tokenizeScalar - - std.mem.splitScalar - - std.mem.splitAny - - std.mem.window 滑动窗口,用法可参考:[[https://zig.news/pyrolistical/new-way-to-split-and-iterate-over-strings-2akh][New way to split and iterate over strings - Zig NEWS]] -- 数据操作 - - 解析数字, =std.fmt.parseInt()= - - 位操作,可以用任意宽度的数字(u1,u2 等), =std.BitStack=, =std.DynamicBitSet= 这两个也非常有用 - -更重要的,作者最后提到 AoC 可能不是学习 Zig 的最好方式: -#+begin_quote -虽然 AoC 非常有趣,但它并不是练习软件工程的方法。 每个 AoC 练习都要求你找到一个问题的解决方案,虽然你需要编写一个程序来解决这个问题, -但你的程序将是一个只需运行一次(一次正确)的一次性脚本。 - -当你的软件需要稳健、优化和可维护时,Zig 就会大显身手,而这些对于 AoC 来说都不重要。 - -因此,请注意,虽然肯定能用 Zig 解决 AoC 问题,而且 Zig 的某些功能甚至能帮助您取得比其他语言更快的进展,但它最终还是针对软件工程进行了优化,而这并不是您在 AoC 中要做的事情。 -#+end_quote -** [[https://floooh.github.io/2024/08/24/zig-and-emulators.html][Zig and Emulators]] -** [[https://jakstys.lt/2024/zig-reproduced-without-binaries/][Zig Reproduced Without Binaries]] -一个很有趣的实验,在 0.10 版本中,Zig 编译器实现了自举,即可以用老版本的 Zig 来编译 Zig 源码,生成最新的 Zig 二进制。这里重新复习一下这个复杂的流程: - -之所以复杂,问题在于老版本的 Zig 从哪里来呢?对于 Zig 来说就是 [[https://github.com/ziglang/zig/blob/master/stage1/zig1.wasm][zig1.wasm]],它是用没自举前的 Zig,利用 LLVM 后端,以 wasm32-wasi 为目标生成的二进制文件。 -为了保证足够小,这里面只保留了 C 后端,这样就得到了一个小到可以放到代码仓库中的 Zig 编译器。这篇文章就是证明这个文件没有被私自篡改过! - -- 之后利用 Zig 团队自己写的 wasm2c.c 把 zig1.wasm 编译成 zig1.c,之后用 cc 编译 zig1.c 就可以得到 stage1 的 zig 编译器 -- 之后再用 zig1 编译 zig 源码,由于 zig1 之后 C 后端,因此这里得到的产物是 zig2.c,再利用 cc 就可以可以 stage2 的 zig。 - zig2 功能上已经完备,但是速度很慢(没有经过 LLVM 优化) -- 最后再用 zig2 继续编译 zig 源码,得到最后的 zig3,这也是我们下载 zig 安装包时包含的版本 -- 如果再继续用 zig3 来编译 zig 源码,得到的 zig4 会和 zig3 一模一样。 - -细节可以参考: -- [[https://ziglang.org/news/goodbye-cpp/][Goodbye to the C++ Implementation of Zig]] -- [[https://www.reddit.com/r/Zig/comments/142gwls/why_is_the_bootstrapping_process_so_complicated/][Why is the bootstrapping process so complicated? : r/Zig]] -* 项目/工具 -- [[https://goreleaser.com/customization/zig-builds/][Builds (Zig) - GoReleaser]] :: 版本发布工具 GoReleaser 支持了 Zig -- [[https://github.com/follgad/zig-ai][FOLLGAD/zig-ai: OpenAI SDK with streaming support]] :: -- [[https://github.com/zouyee/zig-learning][A bunch of links to blog posts, articles, videos, etc for learning Zig]] :: -- [[https://github.com/Super-ZIG/cli][Super-ZIG/cli: Easy command line interface in ZIG.]] :: -- [[https://github.com/deckarep/zigualizer][deckarep/zigualizer]] :: Zigualizer: A music visualizer built with Zig, powered by the FFT algorithm. -- [[https://github.com/freref/fancy-cat][freref/fancy-cat]] :: PDF reader for terminal emulators using the Kitty image protocol -- [[https://github.com/Dr-Nekoma/lyceum][Dr-Nekoma/lyceum]] :: An MMO game written in Erlang (+ PostgreSQL) + Zig (+ Raylib) -* [[https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-11-01..2024-12-01][Zig 语言更新]] diff --git a/content/monthly/202411.smd b/content/monthly/202411.smd new file mode 100644 index 0000000..4fc63cb --- /dev/null +++ b/content/monthly/202411.smd @@ -0,0 +1,93 @@ +--- +.title = "202411", +.date = @date("2024-12-09T21:02:40+0800"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, +.custom = { .lastmod = "2024-12-09T23:27:21+0800" }, +--- + +# [观点/教程]($section.id('观点/教程')) +## [[Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html)]($section.id('[Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html)')) +[JAM](https://github.com/srijan-paul/jam) 作者写的一篇文章,分析里市面上现有的 JS 工具链(bundler、formatter、linter 等),虽然已经很好用,但是不够快。下面是他举的几个例子: +- Lossless, cache efficient syntax trees,现在通用的 JS 语法树表示是 [ESTree](https://github.com/estree/estree),尽管设计上很简洁,但在遍历时不够高效,需要有遍历多次 + 才能得到有用信息(eslint 里就有四次!),而且都是指针的树结构非常不利用重复利用 CPU,Carbon 编译器就有一种更紧凑的 AST 表示。 +- Compile time AST query processing。Lint 的规则大部分都是模式匹配,大部分时候都有多个嵌套的 if 逻辑,为了简化插件开发者,eslint + 采用了一种 [esquery](https://estools.github.io/esquery/) 的语法,示例: + ```js +if ( + node.type == "CallExpression" && + node.callee.type === "MemberExpression" && + node.callee.object.type === "Identifier" && + node.callee.object.name === "child_process" +) { + // many of these checks are to satisfy typescript^ +} + +if (matches( + 'CallExpression[callee.object.name = child_process]', + node + )) { + // much better +} + ``` + esquery 的问题在于执行效率,通过借助于 zig 的 comptime 来在编译器翻译 esquery 就避免了运行时开销。 +## [[Advent of Code in Zig | Loris Cro's Blog](https://kristoff.it/blog/advent-of-code-zig/)]($section.id('[Advent of Code in Zig | Loris Cro\'s Blog](https://kristoff.it/blog/advent-of-code-zig/)')) +一年一度的 AoC 又到了,这篇文章里给出了一些使用的技巧来帮助大家用 Zig 来解决 AoC 问题。 +- 工具链,最新的版本 0.13 和 zls +- 手册,和 [How to read the standard library source code · ziglang/zig Wiki](https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code) +- 使用 `embedFile` 来嵌入输入的测试用例: + ```zig +const std = @import("std"); +const input = @embedFile("path/to/input.txt"); + +pub fn main() !void { + for (input) |byte| { + //... + } +} + ``` +- 分词 + - std.mem.tokenizeScalar + - std.mem.splitScalar + - std.mem.splitAny + - std.mem.window 滑动窗口,用法可参考:[New way to split and iterate over strings - Zig NEWS](https://zig.news/pyrolistical/new-way-to-split-and-iterate-over-strings-2akh) +- 数据操作 + - 解析数字, `std.fmt.parseInt()` + - 位操作,可以用任意宽度的数字(u1,u2 等), `std.BitStack`, `std.DynamicBitSet` 这两个也非常有用 + +更重要的,作者最后提到 AoC 可能不是学习 Zig 的最好方式: +> +虽然 AoC 非常有趣,但它并不是练习软件工程的方法。 每个 AoC 练习都要求你找到一个问题的解决方案,虽然你需要编写一个程序来解决这个问题, +但你的程序将是一个只需运行一次(一次正确)的一次性脚本。 + +当你的软件需要稳健、优化和可维护时,Zig 就会大显身手,而这些对于 AoC 来说都不重要。 + +因此,请注意,虽然肯定能用 Zig 解决 AoC 问题,而且 Zig 的某些功能甚至能帮助您取得比其他语言更快的进展,但它最终还是针对软件工程进行了优化,而这并不是您在 AoC 中要做的事情。 + +## [[Zig and Emulators](https://floooh.github.io/2024/08/24/zig-and-emulators.html)]($section.id('[Zig and Emulators](https://floooh.github.io/2024/08/24/zig-and-emulators.html)')) +## [[Zig Reproduced Without Binaries](https://jakstys.lt/2024/zig-reproduced-without-binaries/)]($section.id('[Zig Reproduced Without Binaries](https://jakstys.lt/2024/zig-reproduced-without-binaries/)')) +一个很有趣的实验,在 0.10 版本中,Zig 编译器实现了自举,即可以用老版本的 Zig 来编译 Zig 源码,生成最新的 Zig 二进制。这里重新复习一下这个复杂的流程: + +之所以复杂,问题在于老版本的 Zig 从哪里来呢?对于 Zig 来说就是 [zig1.wasm](https://github.com/ziglang/zig/blob/master/stage1/zig1.wasm),它是用没自举前的 Zig,利用 LLVM 后端,以 wasm32-wasi 为目标生成的二进制文件。 +为了保证足够小,这里面只保留了 C 后端,这样就得到了一个小到可以放到代码仓库中的 Zig 编译器。这篇文章就是证明这个文件没有被私自篡改过! + +- 之后利用 Zig 团队自己写的 wasm2c.c 把 zig1.wasm 编译成 zig1.c,之后用 cc 编译 zig1.c 就可以得到 stage1 的 zig 编译器 +- 之后再用 zig1 编译 zig 源码,由于 zig1 之后 C 后端,因此这里得到的产物是 zig2.c,再利用 cc 就可以可以 stage2 的 zig。 + zig2 功能上已经完备,但是速度很慢(没有经过 LLVM 优化) +- 最后再用 zig2 继续编译 zig 源码,得到最后的 zig3,这也是我们下载 zig 安装包时包含的版本 +- 如果再继续用 zig3 来编译 zig 源码,得到的 zig4 会和 zig3 一模一样。 + +细节可以参考: +- [Goodbye to the C++ Implementation of Zig](https://ziglang.org/news/goodbye-cpp/) +- [Why is the bootstrapping process so complicated? : r/Zig](https://www.reddit.com/r/Zig/comments/142gwls/why_is_the_bootstrapping_process_so_complicated/) +# [项目/工具]($section.id('项目/工具')) +- [Builds (Zig) - GoReleaser](https://goreleaser.com/customization/zig-builds/) :: 版本发布工具 GoReleaser 支持了 Zig +- [FOLLGAD/zig-ai: OpenAI SDK with streaming support](https://github.com/follgad/zig-ai) :: +- [A bunch of links to blog posts, articles, videos, etc for learning Zig](https://github.com/zouyee/zig-learning) :: +- [Super-ZIG/cli: Easy command line interface in ZIG.](https://github.com/Super-ZIG/cli) :: +- [deckarep/zigualizer](https://github.com/deckarep/zigualizer) :: Zigualizer: A music visualizer built with Zig, powered by the FFT algorithm. +- [freref/fancy-cat](https://github.com/freref/fancy-cat) :: PDF reader for terminal emulators using the Kitty image protocol +- [Dr-Nekoma/lyceum](https://github.com/Dr-Nekoma/lyceum) :: An MMO game written in Erlang (+ PostgreSQL) + Zig (+ Raylib) +# [Zig 语言更新]($section.id('zig-update')) +[2024-11-01..2024-12-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-11-01..2024-12-01) diff --git a/content/monthly/_index.md b/content/monthly/index.smd similarity index 77% rename from content/monthly/_index.md rename to content/monthly/index.smd index 5b59ecb..8bdc4c1 100644 --- a/content/monthly/_index.md +++ b/content/monthly/index.smd @@ -1,8 +1,9 @@ --- -title: 月刊 -type: blog -cascade: - - type: blog +.title = "月刊", +.date = @date("1990-01-01T00:00:00"), +.author = "ZigCC", +.layout = "monthly.shtml", +.draft = false, --- 社区内的最新进展,信息来源:[Zig NEWS](https://zig.news/top/month)、[Zig monthly](https://zigmonthly.org/)、[Lobsters](https://lobste.rs/t/zig)、[Reddit](https://www.reddit.com/r/Zig/)、[Zig weekly newsletter](https://discu.eu/weekly/zig/)、[Ziggit](https://ziggit.dev/)、[用户推荐](https://github.com/orgs/zigcc/discussions/new?category=%E4%BD%9C%E5%93%81%E5%88%86%E4%BA%AB) diff --git a/content/post/0.14.md b/content/post/0.14.md deleted file mode 100644 index 8a78f2e..0000000 --- a/content/post/0.14.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: 0.14 版本更新介绍 -date: 2025-05-21T09:26:22+08:00 -slug: 0.14-intro ---- - -https://ziglang.org/download/0.14.0/release-notes.html - -# 发布概览 - -Zig 0.14.0 版本是经过 **9 个月的工作**,由 **251 位不同的贡献者** 完成,包含 **3467 个提交** 的成果。该版本专注于提升 **健壮性**、**最优性** 和 **可重用性**,并通过 Zig 软件基金会 (Zig Software Foundation) 资助开发。 - -# 核心主题与重要更新 - -1. **提升编译速度与开发效率:** - -- 版本说明强调了两个重要的长期投资:**增量编译 (Incremental Compilation)** 和 **快速 x86 后端 (fast x86 Backend)**。 -- 这两项投资的核心目标是 **“reducing edit/compile/debug cycle latency”**(减少编辑/编译/调试循环延迟)。 -- **增量编译** 功能在本次发布中可以通过 -fincremental 标志选择启用,但目前尚未完全成熟。它在配合文件系统监控时表现良好,尤其是在仅检查编译错误时能显著提升反馈速度。 -- 引用:$ zig build -Dno-bin -fincremental --watch (展示了在大型代码库中快速获得编译错误反馈的示例)。 -- 目前不兼容 usingnamespace,建议用户尽量避免使用。 -- **x86 后端** 在行为测试中表现出色,已经通过了 98% 的测试用例,编译速度显着快于 LLVM 后端,并且支持更好的调试器。它有望在下一个发布周期成为调试模式下的默认后端。可以通过 -fno-llvm 或 use\_llvm = false 启用。 -- 引用:The x86 backend is now passing 1884/1923 (98%) of the behavior test suite compared to the LLVM backend. - -2. **增强目标平台支持 (Target Support):** - -- 这是一个主要主题,Zig 的跨平台编译能力得到了极大扩展。 -- 版本说明详细列出了不同目标平台在语言特性、标准库、代码生成、链接器、调试信息、libc 和 CI 测试等方面的支持级别,采用分层系统 (Tier System) 进行分类(Tier 1 为最高级别)。 -- 引用:A major theme in this Zig release is improved target support; the list of targets that Zig can correctly cross-compile to and run on has been greatly expanded. -- 对 arm/thumb, mips/mips64, powerpc/powerpc64, riscv32/riscv64, 或 s390x 等目标平台的工具链问题、标准库支持缺失和崩溃问题有了显著改进。 -- 更新了目标三元组 (Target Triple Changes) 的命名和支持,以更准确地反映不同平台(如 Windows、Linux 使用 musl libc)的 ABI 和特性。 - -3. **重要的语言特性变化 (Language Changes):** - -- **标记 switch (Labeled Switch):** 允许 switch 语句拥有标签并被 continue 语句指向,从而实现更清晰的状态机实现。关键动机在于生成优化后的代码,特别是通过不同的分支指令帮助 CPU 进行更准确的分支预测,提升性能。 -- 引用:Zig 0.14.0 implements an accepted proposal which allows switch statements to be labeled, and to be targeted by continue statements. -- 引用:This language construct is designed to generate code which aids the CPU in predicting branches between cases of the switch, allowing for increased performance in hot loops... -- 更新 Zig 的 tokenizer 利用此特性带来了 13% 的性能提升。 -- **声明字面量 (Decl Literals):** 扩展了 .foo 语法,不仅可以引用枚举成员,还可以引用目标类型上的任何声明 (const/var/fn)。这使得初始化结构体字段和调用初始化函数更加简洁和安全,特别是避免了无效的默认字段值问题。 -- 引用:Zig 0.14.0 extends the "enum literal" syntax (.foo) to provide a new feature, known as "decl literals". -- 许多现有使用字段默认值的地方可能更适合使用 default 或 empty 等声明来处理,以确保数据不变性。 -- **字段和声明不能共享名称 (Fields and Declarations Cannot Share Names):** 引入了容器类型(struct, union, enum, opaque)的字段和声明不能同名的限制,解决了歧义问题并便于文档生成。 -- 引用:Zig 0.14.0 introduces a restriction that container types (struct , union , enum and opaque ) cannot have fields and declarations ( const / var / fn ) with the same names. -- **@splat 支持数组 (@splat Supports Arrays):** 扩展了 @splat 内置函数,使其可以应用于数组和哨兵终止数组,方便用常量值初始化数组。 -- 引用:Zig 0.14.0 expands the @splat builtin to apply not only to vectors, but to arrays. -- **全局变量可以相互引用地址 (Global Variables can be Initialized with Address of Each Other):** 允许全局变量在初始化时相互引用地址。 -- **@export 操作数现在是指针 (@export Operand is Now a Pointer):** @export 内置函数现在接受一个指针作为操作数,使其用法更清晰和一致,通常只需在旧用法前添加 &。 -- **新的 @branchHint 内置函数,取代 @setCold (New @branchHint Builtin, Replacing @setCold):** 引入 @branchHint 内置函数,允许开发者向优化器提示分支的可能性,有 .none, .likely, .unlikely, .cold, .unpredictable 等选项。这取代了旧的 @setCold。 -- 引用:Zig 0.14.0 introduces a mechanism to communicate this information: the new @branchHint(comptime hint: std.builtin.BranchHint) builtin. -- @branchHint 必须是其所在块或函数的第一个语句。 -- **移除 @fenceStoreLoad Barriers:** 移除 @fence(.StoreLoad),其功能现在可以通过使用 SeqCst 或 Acquire/Release 原子操作来实现。 -- **Packed Struct Equality 和 Packed Struct Atomics:** 允许直接对 Packed Struct 进行相等性比较和原子操作,不再需要 @bitCast 到底层整数类型。 -- **@ptrCast 允许改变切片长度 (@ptrCast Allows Changing Slice Length):** #22706 -- **移除匿名结构体类型,统一元组 (Remove Anonymous Struct Types, Unify Tuples):** 重构匿名结构体字面量和元组的工作方式,使其使用“普通”结构体类型和基于 AST 节点及结构体的等价性。 -- **Calling Convention 增强和 @setAlignStack 被取代 (Calling Convention Enhancements and @setAlignStack Replaced):** std.builtin.CallingConvention 现在是一个标记联合,包含更多目标平台特定的调用约定,并允许通过 CommonOptions 设置栈对齐等选项。.c 调用约定现在是一个声明,可以通过 callconv(.c) 使用 Decl Literals 访问。@setAlignStack 被移除,其功能现在通过调用约定的选项实现。 -- \*_std.builtin.Type 字段重命名和简化 (std.builtin.Type Fields Renamed and Simplify Usage Of ?const anyopaque):_ std.builtin.Type 联合体的字段名称改为小写,并增加了对 default\_value\_ptr 和 sentinel\_ptr 字段的 helper 方法,以简化使用。 -- **不允许非标量哨兵类型 (Non-Scalar Sentinel Types Disallowed):** 哨兵值现在只能是支持 == 操作符的标量类型。 -- **@FieldType 内置函数 (@FieldType builtin):** 新增 @FieldType 内置函数,用于获取给定类型和字段名称的字段类型,取代了 std.meta.FieldType 函数。 -- **@src 获得 Module 字段 (@src Gains Module Field):** std.builtin.SourceLocation 结构体新增 module 字段。 -- **@memcpy 规则调整 ( @memcpy Rules Adjusted):** langspec 定义调整,源和目标元素类型必须内存可强制转换,从而确保是原始复制操作。对 comptime @memcpy 增加了别名检查和更高效的实现。 -- **禁止不安全的内存强制转换 (Unsafe In-Memory Coercions Disallowed):** #22243 -- **callconv, align, addrspace, linksection 不能引用函数参数 (#22264callconv, align, addrspace, linksection Cannot Reference Function Arguments):** #22264 -- **函数调用的分支配额规则调整 (Branch Quota Rules Adjusted for Function Calls):** #22414 - -4. **标准库改进 (Standard Library):** - -- **DebugAllocator 和 SmpAllocator:** 重写了 GeneralPurposeAllocator 并更名为 DebugAllocator,提高了调试模式下的性能。新增了 SmpAllocator,一个针对 ReleaseFast 模式和多线程优化的单例分配器,性能可与 glibc 媲美。 -- **Allocator API 变化 (remap):** std.mem.Allocator.VTable 新增了 remap 函数,允许在可能的情况下进行无需 memcpy 的内存重映射。resize 语义不变。Allocator.VTable 函数现在使用 std.mem.Alignment 类型。 -- **ZON 解析和序列化 (ZON Parsing and Serialization):** std.zon.parse 提供运行时解析 ZON 到 Zig 结构体的功能,std.zon.stringify 提供运行时序列化功能。 -- **运行时页面大小 (Runtime Page Size):** 移除了编译时已知的 std.mem.page\_size,代之以编译时已知的 std.heap.page\_size\_min 和 std.heap.page\_size\_max。std.heap.pageSize() 提供运行时实际页面大小。解决了在 Apple 新硬件上运行 Linux 的支持问题。 -- **Panic 接口 (#22594Panic Interface):** #22594 -- **Transport Layer Security (std.crypto.tls):** #21872 -- **process.Child.collectOutput API 变化 (#21872process.Child.collectOutput API Changed):** API 签名改变,现在将 allocator 作为第一个参数传入。 -- **LLVM Builder API:** LLVM bitcode builder API 移至 std.zig.llvm,方便第三方项目复用。 -- **拥抱“非管理”风格容器 (Embracing "Unmanaged"-Style Containers):** 大部分带有内置 allocator 的标准库容器(如 std.ArrayList, std.ArrayHashMap)已被弃用,推荐使用“非管理”风格容器(如 std.ArrayListUnmanaged, std.ArrayHashMapUnmanaged),并在需要时显式传递 allocator。 -- **std.c 重组 (std.c Reorganization):** 重组了 std.c,使其结构更清晰,并改变了对不存在符号的处理方式(从 @compileError 改为 void 或 {}),移除了标准库中最后一个 usingnamespace 的使用点。 -- **弃用列表 (List of Deprecations):** 列出了大量被弃用或重命名的标准库函数和类型。 -- **Binary Search:** #20927 -- **std.hash\_map 获得 rehash 方法 (#20927std.hash\_map gains a rehash method):** 为解决 HashMap 移除元素后的性能下降问题新增了 rehash 方法(Array Hash Map 没有此问题)。此方法预计在未来不再需要时会被删除。 - -5. **构建系统升级 (Build System):** - -- **基于现有模块创建 Artifacts (Creating Artifacts from Existing Modules):** 修改了构建系统 API,允许从现有的 std.Build.Module 对象创建 Compile 步骤,使模块图的定义更清晰,组件复用更容易。旧的 API 用法已被弃用。 -- 引用:Zig 0.14.0 modifies the build system APIs for creating Compile steps, allowing them to be created from existing std.Build.Module objects. -- **允许包按名称暴露任意 LazyPath (Allow Packages to Expose Arbitrary LazyPaths by Name):** 新增 std.Build.addNamedLazyPath 和 std.Build.Module.namedLazyPath 方法,允许依赖包按名称暴露生成的 LazyPath 给其依赖者使用。 -- **addLibrary 函数:** 新增 addLibrary 函数,取代 addSharedLibrary 和 addStaticLibrary,使得在 build.zig 中更容易切换链接模式,并与 linkLibrary 名称更匹配。 -- **文件系统监控 (File System Watching):** #22105 -- **新包哈希格式 (New Package Hash Format):** 引入新的包哈希格式,包含 32 位 id 和 32 位校验和,用于在去中心化生态系统中唯一标识包。当 fork 项目时,如果上游仍维护,应该重新生成 fingerprint。 -- **WriteFile Step, RemoveDir Step, Fmt Step:** 新增或改进了这些构建步骤。 -- **Breakings:** 多个 installHeader 和 installHeadersDirectory 相关函数签名改变,现在接受 LazyPath。生成的 -femit-h 头文件不再默认发出。 - -6. **编译器和链接器改进 (Compiler and Linker):** - -- **多线程后端支持 (Multithreaded Backend Support):** 部分编译器后端(如 x86 后端)支持在单独线程中运行代码生成,显著提升了编译速度。 -- **LLVM 19:** 升级到 LLVM 19.1.7。 -- **链接器输入文件解析移至前端 (Move Input File Parsing to the Frontend):** 将 GNU ld 脚本处理移至前端,以便在编译开始时了解所有链接器输入,实现编译和链接同时进行。为了避免对所有 .so 文件进行文件系统访问,新增了 -fallow-so-scripts 命令行标志,允许用户选择启用对 .so 脚本的支持。 -- 引用:Moves GNU ld script processing to the frontend to join the relevant library lookup logic, making the libraries within subject to the same search criteria as all the other libraries. - -7. **集成模糊测试器 (Fuzzer):** - -- Zig 0.14.0 集成了一个模糊测试器,目前处于 alpha 状态。通过 --fuzz 命令行选项启用。 -- 可以针对包含 std.testing.fuzz 的单元测试二进制文件进行进程内模糊测试。 -- 提供了 Web UI (http://127.0.0.1:38239/) 显示实时代码覆盖率。 - -8. **Bug 修复与 Toolchain 更新 (Bug Fixes and Toolchain):** - -- 关闭了 416 个 bug 报告。但版本说明坦承 **“This Release Contains Bugs”**,并指出在 1.0.0 版本达到 Tier 1 支持时会增加 bug 策略。 -- **UBSan Runtime:** Debug 模式下默认启用 UBSan 运行时库,为 C 代码的未定义行为提供更详细的恐慌信息和堆栈跟踪。可以通过 -fno-ubsan-rt 和 -fubsan-rt 控制。 -- **compiler\_rt:** 包含了优化的 memcpy 实现。 -- **musl 1.2.5:** 捆绑的 musl 更新并应用了 CVE 修复和目标平台特定补丁。不再捆绑 musl 的 memcpy 文件,而是使用 Zig 的优化实现。 -- **glibc 2.41:** 支持 glibc 2.40 和 2.41 交叉编译,修复了多个与 glibc 相关的问题。 -- **Linux 6.13.4 Headers, Darwin libSystem 15.1, MinGW-w64, wasi-libc:** 更新了捆绑的操作系统头文件和 libc 版本。捆绑了 winpthreads 库。 - -**社区贡献与资助:** - -- 版本说明详细感谢了 **251 位** 为本次发布做出贡献的开发者。 -- 特别感谢了通过经常性捐赠支持 Zig 的个人和组织赞助商,强调了开源社区驱动的重要性。 - -**路线图展望:** - -- 版本说明中穿插提到了未来的一些计划,例如提升增量编译的成熟度、使 x86 后端成为调试模式下的默认选项、改进 ZON 导入,以及未来容器和哈希地图的改进。 -- 最终目标是达到 1.0.0 版本,届时 Tier 1 支持将包含 bug 政策。 - - -# 关于 Zig 0.14.0 版本的常见问题解答 - -## Zig 0.14.0 版本的主要更新和亮点是什么? - -Zig 0.14.0 版本是长达 9 个月开发工作和 3467 次提交的成果,主要亮点包括:显著增强了对多种目标平台的支持,包括 arm/thumb、mips/mips64、powerpc/powerpc64、riscv32/riscv64 和 s390x 等,许多之前存在工具链问题、标准库支持缺失或崩溃的情况现在应该可以正常工作了。此外,该版本在构建系统方面进行了大量升级,并对语言进行了多项重要改进,例如引入了 Labeled Switch 和 Decl Literals 等新特性。为了缩短编辑/编译/调试周期,版本还迈向了两个长期投资目标:增量编译和快速 x86 后端。 - -## Zig 如何对不同目标平台的开发支持进行分级? - -Zig 使用四层系统来对不同目标平台的支持级别进行分类,其中 Tier 1 是最高级别: - -- **Tier 1:** 所有非实验性语言特性都能正常工作。编译器能够独立生成目标平台的机器代码,功能与 LLVM 相当。即使在交叉编译时,该目标平台也有可用的 libc。 -- **Tier 2:** 标准库的跨平台抽象在该目标平台上有实现。该目标平台具备调试信息能力,可以在断言失败和崩溃时生成堆栈跟踪。CI 机器在每次 master 分支提交时都会自动构建和测试该目标平台。 -- **Tier 3:** 编译器可以依赖外部后端(如 LLVM)为该目标平台生成机器代码。链接器可以为该目标平台生成目标文件、库和可执行文件。 -- **Tier 4:** 编译器可以依赖外部后端(如 LLVM)为该目标平台生成汇编源代码。如果 LLVM 将此目标平台视为实验性,则需要从源代码构建 LLVM 和 Zig 才能使用它。 - -## 什么是 Labeled Switch,它有什么优势? - -Labeled Switch 是 Zig 0.14.0 中引入的一项语言特性,允许 switch 语句被标记,并作为 continue 语句的目标。continue :label value 语句会用 value 替换原始的 switch 表达式操作数,并重新评估 switch。尽管在语义上类似于循环中的 switch,但 Labeled Switch 的关键优势在于其代码生成特性。它可以生成帮助 CPU 更准确预测分支的代码,从而提高热循环中的性能,特别是在处理指令分派、评估有限状态自动机 (FSA) 或执行类似基于 case 的评估时。这有助于 branch predictor 更准确地预测控制流。 - -## Decl Literals 是什么,它解决了哪些问题? - -Decl Literals 是 Zig 0.14.0 扩展 "enum literal" 语法 (.foo) 而引入的新特性。现在,一个枚举字面量 .foo 不仅可以引用枚举变体,还可以使用 Result Location Semantics 引用目标类型上的任何声明(const/var/fn)。这在初始化结构体字段时特别有用,可以避免重复指定类型,并有助于避免 Faulty Default Field Values 的问题,确保数据不变量不会因覆盖单个字段而受到破坏。它也支持直接调用函数来初始化值。 - -## Zig 0.14.0 版本在内存分配器方面有哪些值得关注的变化? - -该版本对内存分配器进行了多项改进: - -- **DebugAllocator:** GeneralPurposeAllocator 已被重写并更名为 DebugAllocator,以解决其依赖于编译时已知的页面大小的问题,并提升性能。 -- **SmpAllocator:** 引入了一个新的分配器,专为 ReleaseFast 优化模式和多线程环境设计。它是一个单例,使用全局状态,每个线程拥有独立的空闲列表,并通过原子操作处理线程资源回收,即使线程退出也能恢复数据。其性能与 glibc 相当。 -- **Allocator API Changes (remap):** std.mem.Allocator.VTable 引入了一个新的 remap 函数,允许尝试扩展或收缩内存并可能重新定位,如果无法在不执行内部 memcpy 的情况下完成,则返回 null,提示调用者自行处理复制。同时,resize 函数保持不变。Allocator.VTable 中的所有函数现在使用 std.mem.Alignment 类型代替 u8,增加了类型安全。 -- **Runtime Page Size:** 移除了编译时已知的 std.mem.page\_size,代之以编译时已知的页面大小上下界 std.heap.page\_size\_min 和 std.heap.page\_size\_max。运行时获取页面大小可以使用 std.heap.pageSize(),它会优先使用编译时已知的值,否则在运行时查询操作系统并缓存结果。这修复了对 Asahi Linux 等新硬件上运行 Linux 的支持。 - -## Zig 0.14.0 版本如何改进构建系统,特别是处理模块和依赖关系? - -Zig 0.14.0 版本在构建系统方面有多项重要改进: - -- **Creating Artifacts from Existing Modules:** 修改了构建系统 API,允许从现有的 std.Build.Module 对象创建 Compile 步骤。这使得模块图的定义更加清晰,并且可以更容易地重用图中的组件。 -- **Allow Packages to Expose Arbitrary LazyPaths by Name:** 引入了 std.Build.Step.addNamedLazyPath 方法,允许包暴露命名的 LazyPath,例如生成的源代码文件,供依赖包使用。 -- **New Package Hash Format:** 引入了新的包哈希格式,包括 name、version 和 fingerprint 字段。fingerprint 是一个重要的概念,用于全局唯一标识包,即使在去中心化的生态系统中也能准确识别更新版本。 -- **addLibrary Function:** 引入 addLibrary 函数作为 addSharedLibrary 和 addStaticLibrary 的替代,允许在 build.zig 中更容易地切换链接模式,并与 linkLibrary 函数名称保持一致。 -- **Import ZON:** ZON 文件现在可以在编译时通过 @import("foo.zon") 导入,前提是结果类型已知。 - -## Zig 0.14.0 版本在编译器后端和编译速度方面有哪些进展? - -该版本在编译器后端和编译速度方面取得了进展: - -- **Multithreaded Backend Support:** 部分编译器后端,如 x86 Backend,现在支持在单独的线程中运行代码生成,这显著提高了编译速度。 -- **Incremental Compilation:** 引入了增量编译特性,可以通过 -fincremental 标志启用。尽管尚未默认启用,但结合文件系统监听,可以显著缩短修改代码后的重新分析时间,提供快速的编译错误反馈。 -- **x86 Backend:** x86 后端在行为测试套件中的通过率已接近 LLVM 后端,并且在开发时通常比 LLVM 后端提供更快的编译速度和更好的调试器支持。虽然尚未默认选中,但鼓励用户尝试使用 -fno-llvm 或在构建脚本中设置 use\_llvm = false 来启用。 - -## Zig 0.14.0 版本在工具链和运行时方面有哪些值得注意的更新? - -该版本在工具链和运行时方面也有多项更新: - -- **UBSan Runtime:** Zig 现在为 UBSan 提供了运行时库,在 Debug 模式下默认启用,可以在 C 代码触发未定义行为时提供详细的错误信息和堆栈跟踪。 -- **LLVM 19:** Zig 已升级到 LLVM 19.1.7 版本。 -- **musl 1.2.5:** 更新了捆绑的 musl 版本,并应用了安全补丁和目标平台特定补丁。 -- **glibc 2.41:** 支持 cross-compiling glibc 2.40 和 2.41 版本,并修复了多个问题,提高了与 glibc 的兼容性。 -- **Linux 6.13.4 Headers:** 包含了 Linux 内核头文件版本 6.13.4。 -- **Darwin libSystem 15.1:** 包含了 Xcode SDK 版本 15.1 的 Darwin libSystem 符号。 -- **MinGW-w64:** 更新了捆绑的 MinGW-w64 版本,并捆绑了 winpthreads 库,支持 cross-compiling 到 thumb-windows-gnu。 -- **wasi-libc:** 更新了捆绑的 wasi-libc 版本。 -- **Optimized memcpy:** 提供了优化的 memcpy 实现,不再捆绑 musl 的 memcpy 文件。 -- **Integrated Fuzzer:** 集成了 alpha 质量的 fuzzer,可以通过 --fuzz CLI 选项使用,并提供一个 fuzzer Web UI 显示实时代码覆盖率。 - -# 总结 - -Zig 0.14.0 版本是向 1.0.0 版本迈进的重要一步,在性能优化(尤其是编译速度)、跨平台支持、语言特性和标准库方面都带来了显著改进。增量编译和快速 x86 后端是关键的长期投资,旨在提升开发者体验。新语言特性如 Labeled Switch 和 Decl Literals 提供了更强大和安全的编程模式。标准库的重组和容器的调整反映了社区的使用模式和最佳实践。构建系统也获得了重要升级,使模块管理和依赖处理更加灵活。尽管仍存在已知 bug,但 Zig 社区在本次发布中展示了活跃的开发和持续的进步。 diff --git a/content/post/2023-09-05-bog-gc-1-en.md b/content/post/2023-09-05-bog-gc-1-en.md deleted file mode 100644 index 809c181..0000000 --- a/content/post/2023-09-05-bog-gc-1-en.md +++ /dev/null @@ -1,193 +0,0 @@ ---- -title: Bog GC Design -author: Feng Wenxuan -date: "2023-09-05T16:40:50+0800" -use-mathjax: true ---- - -# Bog GC Design - -Bog is a small scripting language developed using Zig. Its GC design is inspired by a paper titled [An efficient of Non-Moving GC for Function languages](https://www.pllab.riec.tohoku.ac.jp/papers/icfp2011UenoOhoriOtomoAuthorVersion.pdf). - -## Overview - -1. Introduction - - Design of the Heap - - Types of GC - - Design of Bitmap -2. Implementation - -## Introduction - -GC stands for garbage collection, which is primarily a memory management strategy for the `heap` region. Memory allocations in the heap are done in exponentially increasing sizes, with a special sub-heap dedicated solely to very large objects. One advantage of this approach might be its efficiency in handling memory requests of various sizes. - -Represented as a formula: - -$$ -Heap = (M, S, (H_3, H_4, ..., H_{12})) -$$ - -Where: - -1. M: is a special region dedicated to storing large objects. -2. S: represents the free space. -3. H: is the sub-heap used for storing smaller objects. $H_i$ represents the sub-heap of size $2^i$, where each addition is twice the size of the previous. - -```mermaid -graph TD - A[Heap] - B[M] - C[S] - D[H:Sub Heap] - - A --> B - A --> C - A --> D - D --> H3 - D --> H4 - D --> H5 - D --> Hi -``` - -```bash -+------------+ -| Container | -| +------+ | -| |Box 1| | -| +------+ | -| +------+ | -| |Box 2| | -| +------+ | -| +------+ | -| |Box 3| | -| +------+ | -+------------+ -``` - -### Memory Sub-Heap Pool - -We've designed a memory resource pool. This pool consists of numerous allocation segments of fixed size. It means that, regardless of how much space a sub-heap requests, it will request it in units of these fixed-size "segments". For instance, if the allocation segments in the pool are of size 1MB, then a sub-heap might request space in sizes of 1MB, 2MB, 3MB, etc., rather than requesting non-integer multiples like 1.5MB or 2.5MB. - -The dynamic allocation and reclaiming of space by the sub-heaps from a resource pool made up of fixed-size segments provide greater flexibility and may enhance the efficiency of memory utilization. - -### Types of GC - -There are many common types of GC. - -In terms of **bitmap recorded data**, there exists "Generational Garbage Collection". In Generational Garbage Collection, "generations" or "ages" do not refer to the bitmap. They actually denote portions of the memory used to store objects. Based on the lifespan of objects, GC categorizes them into different generations. The fundamental idea behind this strategy is that newly created objects will become garbage sooner, whereas older objects might live longer. - -Generally, in Generational GC, there are two primary generations: - -1. **Young Generation**: Newly created objects are initially placed here. The Young Generation space is usually smaller and is garbage collected frequently. - -2. **Old or Tenured Generation**: After objects have lived in the Young Generation for a sufficient amount of time and have survived several garbage collections, they are moved to the Old Generation. The Old Generation space is typically larger than the Young Generation, and garbage collection occurs less frequently since objects in the Old Generation are expected to have a longer lifespan. - -Bitmaps serve as a tool here, used to track and manage which objects in each generation are active (i.e., still in use) and which are garbage. When the algorithm extends to Generational GC, multiple bitmaps can be maintained for different generations within the same heap space. In this way, active and inactive objects of each generation can be tracked individually. - -"Maintain one bitmap for the Young Generation and another for the Old Generation." This allows us to consider each generation separately during garbage collection, optimizing the efficiency and performance of the collection process. - -In terms of **moving existing data**, there are "Moving GC" and "Non-Moving GC". Moving GC relocates living objects to new memory addresses and compresses memory, while Non-Moving GC doesn't move living objects, allowing other languages to seamlessly call data in memory. - -Within this Moving GC category, there are "Generational Copying Collectors" and "Cheney's Copying Collector". - -Generational Copying Collector: It is a common method of garbage collection, especially in functional programming languages. It assumes that newly created objects will soon become unreachable (i.e., "die"), whereas older objects are more likely to persist. Thus, memory is divided into two or more "generations". New objects are created in the "Young Generation", and when they live long enough, they are moved to the "Old Generation". - -Cheney's Copying Collector: This is a garbage collector used for semi-space. It works by dividing the available memory in half and only allocating objects in one half. When this half is exhausted, the collector performs garbage collection by copying active objects to the other half. The original half is then completely emptied, becoming the new available space. Cheney's collector is particularly suited for handling data with short lifespans because it quickly copies only the active data while ignoring the dead data. This makes it highly efficient for its "minor collections" (collections that only reclaim Young Generation data) when dealing with programs that handle a large amount of short-lifetime data, such as functional programs. **The advantage of this method is that it can efficiently handle memory fragmentation since memory becomes continuously occupied by copying active objects to new locations.** - -Characteristics: - -- **Any precise copy gc requires the runtime system to locate and update all pointers of every heap-allocated data.** -- In traditional garbage collection strategies (Moving GC), compaction is a commonly used technique, moving active objects into a contiguous region of memory, thereby freeing up unused memory. In other words, it consolidates memory fragmentation. - -In Non-Moving GC, there's "Mark-Sweep". - -**The absence of compaction and object movement is very important**, as the value of a pointer (i.e., the memory address of an object) remains fixed and there's no time spent updating moved addresses. This makes Non-Moving GC highly suitable for languages that need to interact with other languages, as they can access objects in memory without the need for extra work. Moreover, the feature of not moving objects is beneficial for supporting multiple native threads. In a multi-threaded environment, if the location of an object in memory keeps shifting, coordination and synchronization between threads become more complicated. Therefore, avoiding object movement simplifies multithreaded programming. - -The benefits are as follows: - -1. **Lock Simplification**: In a multi-threaded environment, if an object needs to be moved (e.g., during the compaction phase of garbage collection), we need to ensure other threads cannot access this object while it's moving. This might require complex locking strategies and synchronization mechanisms. However, if objects never move, this synchronization need is reduced, making locking strategies simpler. - -2. **Pointer Stability**: In multi-threaded programs, threads might share pointers or references to objects. If an object moves in memory, all threads sharing that object would need to update their pointers or references. This not only adds synchronization complexity but might also introduce errors, like dangling pointers. If objects don't move, these pointers remain consistently valid. - -3. **Predictability and Performance**: Not having to move objects means memory access patterns are more stable and predictable. In multi-threaded programs, predictability is a valuable trait as it can reduce contention between threads, improving overall program performance. - -4. **Reduced Pause Times**: Object movement in garbage collection can lead to noticeable pauses in an application because all threads must be paused to move objects safely. In a multi-threaded environment, this pause might be more pronounced as more threads might actively use objects. Not moving objects reduces such pauses. - -5. **Interoperability with Other Languages or Systems**: If your multi-threaded application interoperates with other languages (like C or C++) or systems, having objects in stable locations becomes even more crucial since external code might rely on the fact that objects aren't moving. - -However, Non-Moving GC has its disadvantages: - -1. **Memory Fragmentation**: Since objects don't move, spaces in memory might become non-contiguous. This could lead to memory fragmentation, decreasing memory usage efficiency. - -2. **Memory Allocation**: Due to fragmentation, memory allocation can become more complicated. For instance, if there isn't enough contiguous space to meet an allocation request, the allocator might need to do more work to find available space. This might decrease allocation performance. - -3. **Memory Usage**: Due to fragmentation, memory usage can become less efficient. For instance, if there's a large object without enough contiguous space to store it, it might get split into multiple fragments which could be assigned to different contiguous spaces, potentially decreasing memory utilization. - -4. **Memory Overhead**: Due to fragmentation, memory overhead can become less efficient. For instance, if there's a large object without enough contiguous space to store it, it might get split into multiple fragments which could be assigned to different contiguous spaces, potentially decreasing memory utilization. - -…… - -To address these problems requires many complicated steps, which won't be elaborated on here. We'll focus on Bog's GC for the explanation. - -### Meta Bitmap - -"Meta Bitmap" or "meta-level bitmaps". This is a higher-level bitmap that summarizes the contents of the original bitmap. This hierarchical structure is similar to the inode mapping in file systems or the use of multi-level page tables in computer memory management. - -For instance, consider a simple bitmap: `1100 1100`. A meta-level bitmap might represent how many free blocks are in every 4 bits. In this scenario, the meta-level bitmap could be `1021` (indicating there's 1 free block in the first 4 bits, 2 free blocks in the second 4 bits, and so on). - -The system doesn't just blindly start searching from the beginning of the bitmap for a free bit; it remembers the last-found position. This way, the next search can begin from this position, speeding up the search process further. This means that the time needed to find the next free bit remains approximately the same, regardless of memory size, which is a very efficient performance characteristic. - -What about the worst-case scenario? - -Let's design for a 32-bit architecture. A 32-bit architecture means that the computer's instruction set and data path are designed to handle data units 32 bits wide. Therefore, when operating on 32-bit data units (like an integer or part of a bitmap), such an architecture can typically process all 32 bits at once. This results in logarithmic operations based on 32, because for larger data sections (like a bitmap), operations might need to proceed in blocks/chunks of 32 bits. **The search time is logarithmically related to the size of segmentSize**. - -For example, if a bitmap is 320 bits long, then on a 32-bit architecture, the worst-case scenario might require checking 10 blocks of 32 bits to find a free bit. This can be represented by log32(320), which results in 10. - -### Bitmap - -Since Bog's GC is essentially still based on "Mark-Sweep", using bitmaps to record data is indispensable. In Bog, we adopted the method of "bitmap records data" for GC. And to improve efficiency, we introduced the concept of meta-bitmaps, where every 4 elements correspond to a meta-bitmap, recording the occupancy status of multiple spaces, and increasing the depth based on the object age in the heap. - -### Implementation - -In reality, Bog's design is a bit more complex. Here are sample in practical code: - -```zig -const Page = struct { - const max_size = 1_048_576; - comptime { - // 2^20, 1MiB - assert(@sizeOf(Page) == max_size); - } - const val_count = @divFloor(max_size - @sizeOf(u32) * 2, (@sizeOf(Value) + @sizeOf(State))); - const pad_size = max_size - @sizeOf(u32) * 2 - (@sizeOf(Value) + @sizeOf(State)) * val_count; - ... -} -``` - -1. `max_size`: Represents the maximum number of bytes a Page can store. We have defined a constant to represent the size of 1 MiB and ensure at compile time that the size of the Page type is exactly 1 MiB. Otherwise, a compile-time error will be triggered. -2. `val_count`: Represents the number of Value objects a Page can store. -3. `pad_size`: Represents the size of the unused space remaining in the Page after storing the maximum number of Value objects. - -```zig - const State = enum { - empty, - white, - gray, - black, - }; - const List = std.ArrayListUnmanaged(*Page); - meta: [val_count]State, - __padding: [pad_size]u8, - free: u32, - marked: u32, - values: [val_count]Value, -``` - -1. An enumeration type named `State` is defined, which has four possible values: empty, white, gray, and black. - - In the context of Garbage Collection (GC), these states are typically related to the status of objects during the GC process. For instance, in generational garbage collection, an object might be marked as "white" (unvisited/pending), "gray" (visited but its references not yet processed), or "black" (processed). -2. `List`: Stores pointers of the Page type. -3. `meta`: Represents the state of each Value object within a Page. Here, we use an enum type to represent the state, and since there are only 4 states, they can be represented using 2 bits. Thus, we can use a u32 to represent the state of all Value objects within a Page. Each State potentially corresponds to the status of a Value object in the `values` field. -4. A `__padding` field, used to pad extra memory space. Its size is determined by the previously mentioned `pad_size` and is an array of bytes (u8). This is commonly used to ensure memory alignment of data structures. -5. `free`: Represents the number, index, or other information related to free or available spaces concerning memory management. -6. `marked`: Represents the number, index, or other information about marked spaces, used during the garbage collection process to determine whether to continue checking values on this page. -7. `values`: Represents the Value objects in a Page. It's an array of Value objects, the size of which is determined by `val_count`. diff --git a/content/post/2023-09-05-bog-gc-1.md b/content/post/2023-09-05-bog-gc-1.md deleted file mode 100644 index aa1d645..0000000 --- a/content/post/2023-09-05-bog-gc-1.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -title: Bog GC 设计 -- 概念篇 -author: 文轩 -date: "2023-09-05T16:38:36+0800" -math: true ---- - -[Bog](https://github.com/Vexu/bog) 是一款基于 Zig 开发的小型脚本语言。它的 GC 设计受到一篇论文[An efficient of Non-Moving GC for Function languages](https://www.pllab.riec.tohoku.ac.jp/papers/icfp2011UenoOhoriOtomoAuthorVersion.pdf)的启发。 - -## 梗概 - -1. 概述 - - Heap 的设计 - - GC 的类别 - - Bitmap 的设计 -2. 实现 - -## 概述 - -GC 是一种垃圾回收的机制,主要是针对`heap`区域的内存管理策略。在堆中的内存分配是按照指数级增长的大小进行的,此外还有一个专门用于非常大对象的特殊子堆。这种方法的一个优点可能是它可以高效地处理各种大小的内存请求。 - -以公式来进行表示: - -$$ -Heap = (M, S, (H_3, H_4, ..., H_{12})) -$$ - -其中: - -1. M 是特殊的区域,用于存储大对象 -2. S 是空闲区域 -3. H 是子堆,用于存储小对象,$H_i$表示大小为$2^i$的子堆,每次新增都为之前的两倍 - -```mermaid -graph TD - A[Heap] - B[M] - C[S] - D[H:Sub Heap] - - A --> B - A --> C - A --> D - D --> H3 - D --> H4 - D --> H5 - D --> Hi -``` - -```bash -+------------+ -| Container | -| +------+ | -| |Box 1| | -| +------+ | -| +------+ | -| |Box 2| | -| +------+ | -| +------+ | -| |Box 3| | -| +------+ | -+------------+ -``` - -### 内存子堆池 - -我们设计一个内存的资源池。这个池由许多固定大小的分配段组成。这意味着,无论子堆请求多少空间,它都会以这些固定大小的“段”为单位来请求。例如,如果池中的分配段大小为 1MB,那么一个子堆可能会请求 1MB、2MB、3MB 等大小的空间,而不是请求 1.5MB 或 2.5MB 这样的非整数倍的大小。 - -而子堆从一个由固定大小的段组成的资源池中动态分配和回收空间,这种策略可以提供更高的灵活性,并可能提高内存使用的效率。 - -### GC 的类别 - -常见的 GC 有很多类型。 - -以**位图记录数据**来说,有“代际垃圾收集 GC”。在代际垃圾收集(Generational Garbage Collection)中,"代"或"世代"并不是指位图。它们实际上是指内存中的一部分,用于存储对象。基于对象的生存周期,GC 把它们分为不同的代。这种策略背后的基本思想是,新创建的对象很快就会变为垃圾,而旧对象则可能存活得更久。 - -一般来说,在代际 GC 中,有两个主要的代: - -1. **新生代(Young Generation)**:新创建的对象首先被放置在这里。新生代空间通常较小,并且经常进行垃圾收集。 - -2. **老年代(Old or Tenured Generation)**:当对象在新生代中存活了足够长的时间并经历了多次垃圾收集后,它们会被移动到老年代。老年代空间通常比新生代大,并且垃圾收集的频率较低,因为预期老年代中的对象会有更长的生命周期。 - -位图在这里是一个工具,用于跟踪和管理每一代中哪些对象是活跃的(即仍在使用中)和哪些是垃圾。当算法扩展为代际 GC 时,可以为同一个堆空间的不同代维护多个位图。这样,每个代的活动和非活动对象都可以被单独地跟踪。 - -“为新生代维护一个位图,为老年代维护另一个位图”。这使得在进行垃圾收集时,我们可以单独地考虑每一个代,从而优化垃圾收集的效率和性能。 - -以是否**移动旧有的数据**来说,有“移动 GC”和“非移动 GC”。移动 GC 会将存活的对象移动到新的内存地址、压缩内存,而非移动 GC 则不会移动存活的对象,可以无缝由其他语言来调用内存里的数据。 - -在这种移动 GC 中,有“分代复制收集器”和“Cheney 复制收集器”。 - -分代复制收集器(Generational Copying Collector): 是垃圾收集的一种常见方法,特别是在函数式编程语言中。它假设新创建的对象很快就会变得不可达(即“死亡”),而老的对象则更可能持续存在。因此,内存被分成两个或更多的“代”,新对象在“新生代”中创建,当它们存活足够长的时间时,它们会被移到“老生代”。 - -Cheney 的拷贝收集器: 是一种用于半区(semi-space)的拷贝垃圾收集器。它的工作原理是将可用内存分为两半,并且只在其中一半中分配对象。当这一半用完时,收集器通过拷贝活跃对象到另一半空间来进行垃圾收集。然后,原先的那一半空间就完全清空,成为新的可用空间。Cheney 的收集器特别适用于处理短生命周期的数据,因为它可以快速地只拷贝活跃的数据,而忽略死亡的数据。这使得它在处理大量短生命周期数据的程序(例如函数式程序)时,对于其“次要收集”(minor collection,即仅仅回收新生代数据的收集)非常高效。**这种方法的优势在于它可以有效地处理内存碎片,因为通过复制活动对象到新位置,内存会被连续地占用。** - -特点: - -- **任何精确的 copy gc 都需要 runtime 系统定位和更新每个堆分配数据的所有指针** -- 在传统的垃圾收集策略(移动 GC)中,压缩是一种常用的技术,它会将活跃的对象移到内存的一个连续区域中,从而释放出未使用的内存。换言之,就是整理内存碎片。 - -在非移动 GC 中,有“标记-清除”。 - -**不需要进行压缩和对象移动这一特性是非常重要**,指针的值(即对象的内存地址)是固定的,也不需要花时间更新移动的地址。这使得非移动 GC 非常适合于需要与其他语言进行交互的语言,因为它们可以在不需要额外的工作的情况下访问内存中的对象。而且不需要移动对象的这一特性对于支持多原生线程也是有益的。在多线程环境中,如果对象在内存中的位置不断移动,那么线程之间的协调和同步将变得更加复杂。因此,避免对象移动可以简化多线程编程。 - -有益的原因如下: - -1. **锁的简化**: 在一个多线程环境中,如果对象需要移动(例如,在垃圾收集的压缩阶段),那么我们需要确保其他线程在对象移动时不能访问这个对象。这可能需要使用复杂的锁策略和同步机制。但是,如果对象永远不移动,我们就可以减少这种同步的需求,使得锁策略更简单。 - -2. **指针的稳定性**: 在多线程程序中,线程之间可能会共享指向对象的指针或引用。如果对象在内存中移动了,那么所有共享该对象的线程都需要更新其指针或引用。这不仅会增加同步的复杂性,而且可能会引入错误,如野指针。如果对象不移动,这些指针就会始终有效。 - -3. **预测性和性能**: 不需要移动对象意味着内存访问模式更加稳定和可预测。在多线程程序中,预测性是一个宝贵的特性,因为它可以减少线程之间的争用,从而提高程序的整体性能。 - -4. **减少暂停时间**: 垃圾收集中的对象移动可能导致应用程序的明显暂停,因为必须暂停所有线程来安全地进行移动。在多线程环境中,这种暂停可能更加明显,因为有更多的线程可能正在活跃地使用对象。不移动对象可以减少这种暂停。 - -5. **与其他语言或系统的互操作**: 如果您的多线程应用程序与其他语言(如 C 或 C++)或系统进行互操作,那么对象的稳定位置将更加重要,因为外部代码可能依赖于对象不移动的事实。 - -但同样的,非移动 GC 也有缺点: - -1. **内存碎片**: 由于对象不会移动,因此内存中的空间可能会变得不连续。这可能会导致内存碎片,从而降低内存使用效率。 -2. **内存分配**: 由于内存碎片,内存分配可能会变得更加复杂。例如,如果没有足够的连续空间来满足分配请求,那么分配器可能需要进行更多的工作来查找可用的空间。这可能会导致分配的性能下降。 -3. **内存使用**: 由于内存碎片,内存使用可能会变得更加低效。例如,如果有一个大对象,但是没有足够的连续空间来存储它,那么它可能会被分成多个碎片,这些碎片可能会被分配给不同的连续空间。这可能会导致内存使用率降低。 -4. **内存占用**: 由于内存碎片,内存占用可能会变得更加低效。例如,如果有一个大对象,但是没有足够的连续空间来存储它,那么它可能会被分成多个碎片,这些碎片可能会被分配给不同的连续空间。这可能会导致内存使用率降低。 - -为了解决这些问题需要很多复杂的步骤,在此不多赘述。单以 Bog 的 GC 来讲解。 - -### Meta Bitmap - -“元级位图”或“meta-level bitmaps”。这是一个更高级别的位图,用于汇总原始位图的内容。这种层次化的结构类似于文件系统中的 inode 映射或多级页表在计算机内存管理中的使用。 - -例如,考虑一个简单的位图:`1100 1100`。一个元级位图可能表示每 4 位中有多少个空闲块。在这种情况下,元级位图可能是 `1021`(表示第一个 4 位中有 1 个空闲块,第二个 4 位中有 2 个空闲块,以此类推)。 - -系统不仅仅是盲目地从位图的开始处查找空闲位,而是记住上一次查找到的位置。这样,下次查找可以从这个位置开始,进一步加速查找过程。这意味着无论内存大小如何,找到下一个空闲位所需的时间都大致相同,这是一个非常高效的性能特性。 - -如果是最坏的情况呢? - -以 32 位架构来设计。32 位架构意味着计算机的指令集和数据路径设计为处理 32 位宽的数据单元。因此,当操作 32 位数据单元(例如一个整数或位图的一部分)时,这样的架构通常可以一次性处理所有 32 位。这导致以 32 为底的对数操作,因为对于较大的数据段(如一个位图),操作可能需要按 32 位的块/段进行。**查找时间与 segmentSize 的大小成对数关系**。 - -例如,如果一个位图有 320 位,那么在 32 位架构上,最坏的情况可能需要检查 10 个 32 位块才能找到一个空闲位。这可以通过 $\log_{32}(320)$来表示,结果是 10。 - -### Bitmap - -由于 Bog 的 GC 本质上还是采用了“标记-清除”,所以利用位图来记录数据是必不可少的。在 Bog 中,我们采用了“位图记录数据”的方式来进行 GC。而为了提高效率,我们增加了元位图的概念,即每 4 个元素对应一个元位图,用于记录多空间的占用状态,并且根据 heap 的对象时间增加深度。 - -### 实现 - -实际上,在 Bog 的设计中,要更加复杂一些。我们增加了 - -```zig -const Page = struct { - const max_size = 1_048_576; - comptime { - // 2^20, 1MiB - assert(@sizeOf(Page) == max_size); - } - const val_count = @divFloor(max_size - @sizeOf(u32) * 2, (@sizeOf(Value) + @sizeOf(State))); - const pad_size = max_size - @sizeOf(u32) * 2 - (@sizeOf(Value) + @sizeOf(State)) * val_count; - ... -} -``` - -1. `max_size`: 表示一个 Page 能够存储的最大字节数。我们定义了一个常量来表示 1 MiB 的大小,并在编译时确保 Page 类型的大小恰好为 1 MiB。否则将会触发编译时错误。 -2. `val_count`: 表示一个 Page 能够存储的 Value 对象的数量 -3. `pad_size`: 表示在存储了最大数量的 Value 对象后,Page 剩余的未使用的空间大小 - -```zig - - const State = enum { - empty, - white, - gray, - black, - }; - const List = std.ArrayListUnmanaged(*Page); - meta: [val_count]State, - __padding: [pad_size]u8, - free: u32, - marked: u32, - values: [val_count]Value, -``` - -1. 定义了一个名为 State 的枚举类型,它有四个可能的值:empty、white、gray 和 black。 - - 在垃圾收集(GC)的上下文中,这些状态通常与对象在 GC 过程中的状态有关。例如,在分代垃圾收集中,对象可能会被标记为 "white"(未访问/待处理),"gray"(已访问但其引用尚未处理)或 "black"(已处理)。 -2. `List`: 存储 Page 类型的指针 -3. `meta`: 表示一个 Page 中每个 Value 对象的状态。这里我们使用了一个枚举类型来表示状态,因为状态只有 4 种,所以可以用 2 位来表示。因此,我们可以使用一个 u32 来表示一个 Page 中所有 Value 对象的状态。这样,我们就可以使用一个 u32 来表示一个 Page 中所有 Value 对象的状态。每一个 State 可能对应一个在 values 字段中的 Value 对象的状态。 -4. `__padding` 的字段,用于填充额外的内存空间。它的大小由之前提到的 pad_size 决定,且是一个字节(u8)数组。这通常用于确保数据结构的内存对齐。 -5. `free`: 表示空闲或可用的空间数量、索引或其他与内存管理相关的信息 -6. `marked`: 表示已标记的空间数量、索引或其他与内存管理相关的信息,在垃圾收集的过程中用于检测是否应继续在此页面中检查值 -7. `values`: 表示一个 Page 中的 Value 对象。它是一个 Value 对象的数组,其大小由 val_count 决定。 diff --git a/content/post/2023-09-05-hello-world.md b/content/post/2023-09-05-hello-world.md deleted file mode 100644 index 0cce8a6..0000000 --- a/content/post/2023-09-05-hello-world.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: 欢迎 Zig 爱好者向本网站供稿 -author: 刘家财 -date: "2023-09-05T16:13:13+0800" ---- - -欢迎社区用户向 ZigCC 供稿(关于 Zig 的任何话题),方便与社区更多人分享。文章会发布在: - -- [ZigCC 网站](https://ziglang.cc) -- [ZigCC 公众号](https://github.com/zigcc/.github/raw/main/zig_mp.png) - -# 供稿方式 - -1. Fork 仓库 https://github.com/zigcc/zigcc.github.io -2. 在 `content/post` 内添加自己的文章(md 或 org 格式均可),文件命名为: `${YYYY}-${MM}-${DD}-${SLUG}.md` -3. 文件开始需要包含一些描述信息,例如[本文件](https://github.com/zigcc/zigcc.github.io/tree/main/content/post/2023-09-05-hello-world.md)中的: - -```plain ---- -title: 欢迎 Zig 爱好者向本网站供稿 -author: 刘家财 -date: '2023-09-05T16:13:13+0800' ---- -``` - -## 本地预览 - -在写完文章后,可以使用 [Hugo](https://gohugo.io/) 进行本地预览,只需在项目根目录执行 `hugo server`,这会启动一个 HTTP 服务,默认的访问地址是: http://localhost:1313/ diff --git a/content/post/2023-09-21-zig-midi.md b/content/post/2023-09-21-zig-midi.md deleted file mode 100644 index 6cd9b03..0000000 --- a/content/post/2023-09-21-zig-midi.md +++ /dev/null @@ -1,986 +0,0 @@ ---- -title: Zig音频之MIDI —— 源码解读 -author: 文轩 -date: "2023-09-21T23:15:02+0800" ---- - -MIDI 是“乐器数字接口”的缩写,是一种用于音乐设备之间通信的协议。而 [zig-midi](https://github.com/Hejsil/zig-midi) 主要是在对 MIDI 的元数据、音频头等元数据进行一些处理的方法上进行了集成。 - -```bash -. -├── LICENSE -├── ReadMe.md -├── build.zig -├── example -│ └── midi_file_to_text_stream.zig -├── midi -│ ├── decode.zig -│ ├── encode.zig -│ ├── file.zig -│ └── test.zig -├── midi.zig -``` - -## 基础 - -在 MIDI 协议中,`0xFF` 是一个特定的状态字节,用来表示元事件(Meta Event)的开始。元事件是 MIDI 文件结构中的一种特定消息,通常不用于实时音频播放,但它们包含有关 MIDI 序列的元数据,例如序列名称、版权信息、歌词、时间标记、速度(BPM)更改等。 - -以下是一些常见的元事件类型及其关联的 `0xFF` 后的字节: - -- `0x00`: 序列号 (Sequence Number) -- `0x01`: 文本事件 (Text Event) -- `0x02`: 版权通知 (Copyright Notice) -- `0x03`: 序列/曲目名称 (Sequence/Track Name) -- `0x04`: 乐器名称 (Instrument Name) -- `0x05`: 歌词 (Lyric) -- `0x06`: 标记 (Marker) -- `0x07`: 注释 (Cue Point) -- `0x20`: MIDI Channel Prefix -- `0x21`: End of Track (通常跟随值`0x00`,表示轨道的结束) -- `0x2F`: Set Tempo (设定速度,即每分钟的四分音符数) -- `0x51`: SMPTE Offset -- `0x54`: 拍号 (Time Signature) -- `0x58`: 调号 (Key Signature) -- `0x59`: Sequencer-Specific Meta-event - -例如,当解析 MIDI 文件时,如果遇到字节 `0xFF 0x03`,那么接下来的字节将表示序列或曲目名称。 - -在实际的 MIDI 文件中,元事件的具体结构是这样的: - -1. `0xFF`: 元事件状态字节。 -2. 元事件类型字节,例如上面列出的 `0x00`, `0x01` 等。 -3. 长度字节(或一系列字节),表示该事件数据的长度。 -4. 事件数据本身。 - -元事件主要存在于 MIDI 文件中,特别是在标准 MIDI 文件 (SMF) 的上下文中。在实时 MIDI 通信中,元事件通常不会被发送,因为它们通常不会影响音乐的实际播放。 - -## Midi.zig - -本文件主要是处理 MIDI 消息的模块,为处理 MIDI 消息提供了基础结构和函数。 - -```zig -const std = @import("std"); - -const mem = std.mem; - -const midi = @This(); - -pub const decode = @import("midi/decode.zig"); -pub const encode = @import("midi/encode.zig"); -pub const file = @import("midi/file.zig"); - -pub const File = file.File; - -test "midi" { - _ = @import("midi/test.zig"); - _ = decode; - _ = file; -} - -pub const Message = struct { - status: u7, - values: [2]u7, - - pub fn kind(message: Message) Kind { - const _kind = @as(u3, @truncate(message.status >> 4)); - const _channel = @as(u4, @truncate(message.status)); - return switch (_kind) { - 0x0 => Kind.NoteOff, - 0x1 => Kind.NoteOn, - 0x2 => Kind.PolyphonicKeyPressure, - 0x3 => Kind.ControlChange, - 0x4 => Kind.ProgramChange, - 0x5 => Kind.ChannelPressure, - 0x6 => Kind.PitchBendChange, - 0x7 => switch (_channel) { - 0x0 => Kind.ExclusiveStart, - 0x1 => Kind.MidiTimeCodeQuarterFrame, - 0x2 => Kind.SongPositionPointer, - 0x3 => Kind.SongSelect, - 0x6 => Kind.TuneRequest, - 0x7 => Kind.ExclusiveEnd, - 0x8 => Kind.TimingClock, - 0xA => Kind.Start, - 0xB => Kind.Continue, - 0xC => Kind.Stop, - 0xE => Kind.ActiveSensing, - 0xF => Kind.Reset, - - 0x4, 0x5, 0x9, 0xD => Kind.Undefined, - }, - }; - } - - pub fn channel(message: Message) ?u4 { - const _kind = message.kind(); - const _channel = @as(u4, @truncate(message.status)); - switch (_kind) { - // Channel events - .NoteOff, - .NoteOn, - .PolyphonicKeyPressure, - .ControlChange, - .ProgramChange, - .ChannelPressure, - .PitchBendChange, - => return _channel, - - // System events - .ExclusiveStart, - .MidiTimeCodeQuarterFrame, - .SongPositionPointer, - .SongSelect, - .TuneRequest, - .ExclusiveEnd, - .TimingClock, - .Start, - .Continue, - .Stop, - .ActiveSensing, - .Reset, - => return null, - - .Undefined => return null, - } - } - - pub fn value(message: Message) u14 { - // TODO: Is this the right order according to the midi spec? - return @as(u14, message.values[0]) << 7 | message.values[1]; - } - - pub fn setValue(message: *Message, v: u14) void { - message.values = .{ - @as(u7, @truncate(v >> 7)), - @as(u7, @truncate(v)), - }; - } - - pub const Kind = enum { - // Channel events - NoteOff, - NoteOn, - PolyphonicKeyPressure, - ControlChange, - ProgramChange, - ChannelPressure, - PitchBendChange, - - // System events - ExclusiveStart, - MidiTimeCodeQuarterFrame, - SongPositionPointer, - SongSelect, - TuneRequest, - ExclusiveEnd, - TimingClock, - Start, - Continue, - Stop, - ActiveSensing, - Reset, - - Undefined, - }; -}; -``` - -这定义了一个名为 Message 的公共结构,表示 MIDI 消息,为处理 MIDI 消息提供了基础结构和函数。它包含三个字段:状态、值和几个公共方法。 - -- kind 函数:根据 MIDI 消息的状态码确定消息的种类。 -- channel 函数:根据消息的种类返回 MIDI 通道,如果消息不包含通道信息则返回 null。 -- value 和 setValue 函数:用于获取和设置 MIDI 消息的值字段。 -- Kind 枚举:定义了 MIDI 消息的所有可能种类,包括通道事件和系统事件。 - -### midi 消息结构 - -我们需要先了解 MIDI 消息的一些背景。 - -在 MIDI 协议中,某些消息的值可以跨越两个 7 位的字节,这是因为 MIDI 协议不使用每个字节的最高位(这通常被称为状态位)。这意味着每个字节只使用它的低 7 位来携带数据。因此,当需要发送一个大于 7 位的值时(比如 14 位),它会被拆分成两个 7 位的字节。 - -`setValue` 这个函数做的事情是将一个 14 位的值(`u14`)拆分为两个 7 位的值,并将它们设置到 `message.values` 中。 - -以下是具体步骤的解释: - -1. **获取高 7 位**:`v >> 7` 把 14 位的值右移 7 位,这样我们就得到了高 7 位的值。 -2. **截断并转换**:`@truncate(v >> 7)` 截断高 7 位的值,确保它是 7 位的。`@as(u7, @truncate(v >> 7))` 确保这个值是 `u7` 类型,即一个 7 位的无符号整数。 - -3. **获取低 7 位**:`@truncate(v)` 直接截断原始值,保留低 7 位。 - -4. **设置值**:`message.values = .{ ... }` 将这两个 7 位的值设置到 `message.values` 中。 - -### 事件 - -针对事件,我们看 enum。 - -```zig - pub const Kind = enum { - // Channel events - NoteOff, - NoteOn, - PolyphonicKeyPressure, - ControlChange, - ProgramChange, - ChannelPressure, - PitchBendChange, - - // System events - ExclusiveStart, - MidiTimeCodeQuarterFrame, - SongPositionPointer, - SongSelect, - TuneRequest, - ExclusiveEnd, - TimingClock, - Start, - Continue, - Stop, - ActiveSensing, - Reset, - - Undefined, - }; - -``` - -这段代码定义了一个名为 `Kind` 的公共枚举类型(`enum`),它描述了 MIDI 中可能的事件种类。每个枚举成员都代表 MIDI 协议中的一个特定事件。这些事件分为两大类:频道事件(Channel events)和系统事件(System events)。 - -这个 `Kind` 枚举为处理 MIDI 消息提供了一个结构化的方法,使得在编程时可以清晰地引用特定的 MIDI 事件,而不是依赖于原始的数字或其他编码。 - -以下是对每个枚举成员的简要说明: - -#### 频道事件 (Channel events) - -1. **NoteOff**:这是一个音符结束事件,表示某个音符不再播放。 -2. **NoteOn**:这是一个音符开始事件,表示开始播放某个音符。 -3. **PolyphonicKeyPressure**:多声道键盘压力事件,表示对特定音符的压力或触摸敏感度的变化。 -4. **ControlChange**:控制变更事件,用于发送如音量、平衡等控制信号。 -5. **ProgramChange**:程序(音色)变更事件,用于改变乐器的音色。 -6. **ChannelPressure**:频道压力事件,与多声道键盘压力相似,但它适用于整个频道,而不是特定音符。 -7. **PitchBendChange**:音高弯曲变更事件,表示音符音高的上升或下降。 - -#### 系统事件 (System events) - -1. **ExclusiveStart**:独占开始事件,标志着一个独占消息序列的开始。 -2. **MidiTimeCodeQuarterFrame**:MIDI 时间码四分之一帧,用于同步与其他设备。 -3. **SongPositionPointer**:歌曲位置指针,指示序列器的当前播放位置。 -4. **SongSelect**:歌曲选择事件,用于选择特定的歌曲或序列。 -5. **TuneRequest**:调音请求事件,指示设备应进行自我调音。 -6. **ExclusiveEnd**:独占结束事件,标志着一个独占消息序列的结束。 -7. **TimingClock**:计时时钟事件,用于节奏的同步。 -8. **Start**:开始事件,用于启动序列播放。 -9. **Continue**:继续事件,用于继续暂停的序列播放。 -10. **Stop**:停止事件,用于停止序列播放。 -11. **ActiveSensing**:活动感知事件,是一种心跳信号,表示设备仍然在线并工作。 -12. **Reset**:重置事件,用于将设备重置为其初始状态。 - -#### 其他 - -1. **Undefined**:未定义事件,可能表示一个未在此枚举中定义的或无效的 MIDI 事件。 - -## decode.zig - -本文件是对 MIDI 文件的解码器, 提供了一组工具,可以从不同的输入源解析 MIDI 文件的各个部分。这样可以方便地读取和处理 MIDI 文件。 - -```zig -const midi = @import("../midi.zig"); -const std = @import("std"); - -const debug = std.debug; -const io = std.io; -const math = std.math; -const mem = std.mem; - -const decode = @This(); - -fn statusByte(b: u8) ?u7 { - if (@as(u1, @truncate(b >> 7)) != 0) - return @as(u7, @truncate(b)); - - return null; -} - -fn readDataByte(reader: anytype) !u7 { - return math.cast(u7, try reader.readByte()) catch return error.InvalidDataByte; -} - -pub fn message(reader: anytype, last_message: ?midi.Message) !midi.Message { - var first_byte: ?u8 = try reader.readByte(); - const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { - first_byte = null; - break :blk status_byte; - } else if (last_message) |m| blk: { - if (m.channel() == null) - return error.InvalidMessage; - - break :blk m.status; - } else return error.InvalidMessage; - - const kind = @as(u3, @truncate(status_byte >> 4)); - const channel = @as(u4, @truncate(status_byte)); - switch (kind) { - 0x0, 0x1, 0x2, 0x3, 0x6 => return midi.Message{ - .status = status_byte, - .values = [2]u7{ - math.cast(u7, first_byte orelse try reader.readByte()) catch return error.InvalidDataByte, - try readDataByte(reader), - }, - }, - 0x4, 0x5 => return midi.Message{ - .status = status_byte, - .values = [2]u7{ - math.cast(u7, first_byte orelse try reader.readByte()) catch return error.InvalidDataByte, - 0, - }, - }, - 0x7 => { - debug.assert(first_byte == null); - switch (channel) { - 0x0, 0x6, 0x07, 0x8, 0xA, 0xB, 0xC, 0xE, 0xF => return midi.Message{ - .status = status_byte, - .values = [2]u7{ 0, 0 }, - }, - 0x1, 0x3 => return midi.Message{ - .status = status_byte, - .values = [2]u7{ - try readDataByte(reader), - 0, - }, - }, - 0x2 => return midi.Message{ - .status = status_byte, - .values = [2]u7{ - try readDataByte(reader), - try readDataByte(reader), - }, - }, - - // Undefined - 0x4, 0x5, 0x9, 0xD => return midi.Message{ - .status = status_byte, - .values = [2]u7{ 0, 0 }, - }, - } - }, - } -} - -pub fn chunk(reader: anytype) !midi.file.Chunk { - var buf: [8]u8 = undefined; - try reader.readNoEof(&buf); - return decode.chunkFromBytes(buf); -} - -pub fn chunkFromBytes(bytes: [8]u8) midi.file.Chunk { - return midi.file.Chunk{ - .kind = bytes[0..4].*, - .len = mem.readIntBig(u32, bytes[4..8]), - }; -} - -pub fn fileHeader(reader: anytype) !midi.file.Header { - var buf: [14]u8 = undefined; - try reader.readNoEof(&buf); - return decode.fileHeaderFromBytes(buf); -} - -pub fn fileHeaderFromBytes(bytes: [14]u8) !midi.file.Header { - const _chunk = decode.chunkFromBytes(bytes[0..8].*); - if (!mem.eql(u8, &_chunk.kind, midi.file.Chunk.file_header)) - return error.InvalidFileHeader; - if (_chunk.len < midi.file.Header.size) - return error.InvalidFileHeader; - - return midi.file.Header{ - .chunk = _chunk, - .format = mem.readIntBig(u16, bytes[8..10]), - .tracks = mem.readIntBig(u16, bytes[10..12]), - .division = mem.readIntBig(u16, bytes[12..14]), - }; -} - -pub fn int(reader: anytype) !u28 { - var res: u28 = 0; - while (true) { - const b = try reader.readByte(); - const is_last = @as(u1, @truncate(b >> 7)) == 0; - const value = @as(u7, @truncate(b)); - res = try math.mul(u28, res, math.maxInt(u7) + 1); - res = try math.add(u28, res, value); - - if (is_last) - return res; - } -} - -pub fn metaEvent(reader: anytype) !midi.file.MetaEvent { - return midi.file.MetaEvent{ - .kind_byte = try reader.readByte(), - .len = try decode.int(reader), - }; -} - -pub fn trackEvent(reader: anytype, last_event: ?midi.file.TrackEvent) !midi.file.TrackEvent { - var peek_reader = io.peekStream(1, reader); - var in_reader = peek_reader.reader(); - - const delta_time = try decode.int(&in_reader); - const first_byte = try in_reader.readByte(); - if (first_byte == 0xFF) { - return midi.file.TrackEvent{ - .delta_time = delta_time, - .kind = midi.file.TrackEvent.Kind{ .MetaEvent = try decode.metaEvent(&in_reader) }, - }; - } - - const last_midi_event = if (last_event) |e| switch (e.kind) { - .MidiEvent => |m| m, - .MetaEvent => null, - } else null; - - peek_reader.putBackByte(first_byte) catch unreachable; - return midi.file.TrackEvent{ - .delta_time = delta_time, - .kind = midi.file.TrackEvent.Kind{ .MidiEvent = try decode.message(&in_reader, last_midi_event) }, - }; -} - -/// Decodes a midi file from a reader. Caller owns the returned value -/// (see: `midi.File.deinit`). -pub fn file(reader: anytype, allocator: *mem.Allocator) !midi.File { - var chunks = std.ArrayList(midi.File.FileChunk).init(allocator); - errdefer { - (midi.File{ - .format = 0, - .division = 0, - .chunks = chunks.toOwnedSlice(), - }).deinit(allocator); - } - - const header = try decode.fileHeader(reader); - const header_data = try allocator.alloc(u8, header.chunk.len - midi.file.Header.size); - errdefer allocator.free(header_data); - - try reader.readNoEof(header_data); - while (true) { - const c = decode.chunk(reader) catch |err| switch (err) { - error.EndOfStream => break, - else => |e| return e, - }; - - const chunk_bytes = try allocator.alloc(u8, c.len); - errdefer allocator.free(chunk_bytes); - try reader.readNoEof(chunk_bytes); - try chunks.append(.{ - .kind = c.kind, - .bytes = chunk_bytes, - }); - } - - return midi.File{ - .format = header.format, - .division = header.division, - .header_data = header_data, - .chunks = chunks.toOwnedSlice(), - }; -} -``` - -1. statusByte: 解析 MIDI 消息的首个字节,来确定是否这是一个状态字节,还是一个数据字节。将一个字节 b 解码为一个 u7 类型的 MIDI 状态字节,如果字节 b 不是一个状态字节,则返回 null。换句话说,midi 的消息是 14 位,如果高 7 位不为空,则是 midi 消息的状态字节。在 MIDI 协议中,消息的首个字节通常是状态字节,但也可能用之前的状态字节(这称为“运行状态”)来解释接下来的字节。因此,这段代码需要确定它是否读取了一个新的状态字节,或者它是否应该使用前一个消息的状态字节。 -2. readDataByte: 从 reader 中读取并返回一个数据字节。如果读取的字节不符合数据字节的规定,则抛出 InvalidDataByte 错误。 -3. message: 从 reader 读取并解码一个 MIDI 消息。如果读取的字节不能形成一个有效的 MIDI 消息,则抛出 InvalidMessage 错误。这是一个复杂的函数,涉及到解析 MIDI 消息的不同种类。 -4. chunk,chunkFromBytes: 这两个函数从 reader 或直接从字节数组 bytes 中解析一个 MIDI 文件块头。 -5. fileHeader, fileHeaderFromBytes: 这两个函数从 reader 或直接从字节数组 bytes 中解析一个 MIDI 文件头。 -6. int: 从 reader 中解码一个可变长度的整数。 -7. metaEvent: 从 reader 中解析一个 MIDI 元事件。 -8. trackEvent: 从 reader 中解析一个 MIDI 轨道事件。它可以是 MIDI 消息或元事件。 -9. file: 用于从 reader 解码一个完整的 MIDI 文件。它首先解码文件头,然后解码所有的文件块。这个函数会返回一个表示 MIDI 文件的结构体。 - -### message 解析 - -```zig -const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { - first_byte = null; - break :blk status_byte; -} else if (last_message) |m| blk: { - if (m.channel() == null) - return error.InvalidMessage; - - break :blk m.status; -} else return error.InvalidMessage; -``` - -这段代码的目的是确定 MIDI 消息的状态字节。它可以是从 `reader` 读取的当前字节,或者是从前一个 MIDI 消息中获取的。这样做是为了支持 MIDI 协议中的“运行状态”,在该协议中,连续的 MIDI 消息可能不会重复状态字节。 - -1. `const status_byte = ...;`: 这是一个常量声明。`status_byte` 将保存 MIDI 消息的状态字节。 - -2. `if (statusByte(first_byte.?)) |status_byte| blk: { ... }`: - -- `statusByte(first_byte.?)`: 这是一个函数调用,它检查 `first_byte` 是否是一个有效的状态字节。`.?` 是可选值的语法,它用于解包 `first_byte` 的值(它是一个可选的 `u8`,可以是 `u8` 或 `null`)。 -- `|status_byte|`: 如果 `statusByte` 函数返回一个有效的状态字节,则这个值会被捕获并赋给这里的 `status_byte` 变量。 -- `blk:`: 这是一个匿名代码块的标签。Zig 允许你给代码块命名,这样你可以从该代码块中跳出。 -- `{ ... }`: 这是一个代码块。在这里,`first_byte` 被设置为 `null`,然后使用 `break :blk status_byte;` 来结束此代码块,并将 `status_byte` 的值赋给外部的 `status_byte` 常量。 - -3. `else if (last_message) |m| blk: { ... }`: - -- 如果 `first_byte` 不是一个状态字节,代码会检查是否存在一个名为 `last_message` 的前一个 MIDI 消息。 -- `|m|`: 如果 `last_message` 存在(即它不是 `null`),它的值将被捕获并赋给 `m`。 -- `{ ... }`: 这是另一个代码块。在这里,它检查 `m` 是否有一个通道。如果没有,则返回一个 `InvalidMessage` 错误。否则,使用 `break :blk m.status;` 结束此代码块,并将 `m.status` 的值赋给外部的 `status_byte` 常量。 - -4. `else return error.InvalidMessage;`: 如果 `first_byte` 不是状态字节,并且不存在前一个消息,那么返回一个 `InvalidMessage` 错误。 - -## encode.zig - -本文件用于将 MIDI 数据结构编码为其对应的二进制形式。具体来说,它是将内存中的 MIDI 数据结构转换为 MIDI 文件格式的二进制数据。 - -```zig -const midi = @import("../midi.zig"); -const std = @import("std"); - -const debug = std.debug; -const io = std.io; -const math = std.math; -const mem = std.mem; - -const encode = @This(); - -pub fn message(writer: anytype, last_message: ?midi.Message, msg: midi.Message) !void { - if (msg.channel() == null or last_message == null or msg.status != last_message.?.status) { - try writer.writeByte((1 << 7) | @as(u8, msg.status)); - } - - switch (msg.kind()) { - .ExclusiveStart, - .TuneRequest, - .ExclusiveEnd, - .TimingClock, - .Start, - .Continue, - .Stop, - .ActiveSensing, - .Reset, - .Undefined, - => {}, - .ProgramChange, - .ChannelPressure, - .MidiTimeCodeQuarterFrame, - .SongSelect, - => { - try writer.writeByte(msg.values[0]); - }, - .NoteOff, - .NoteOn, - .PolyphonicKeyPressure, - .ControlChange, - .PitchBendChange, - .SongPositionPointer, - => { - try writer.writeByte(msg.values[0]); - try writer.writeByte(msg.values[1]); - }, - } -} - -pub fn chunkToBytes(_chunk: midi.file.Chunk) [8]u8 { - var res: [8]u8 = undefined; - mem.copy(u8, res[0..4], &_chunk.kind); - mem.writeIntBig(u32, res[4..8], _chunk.len); - return res; -} - -pub fn fileHeaderToBytes(header: midi.file.Header) [14]u8 { - var res: [14]u8 = undefined; - mem.copy(u8, res[0..8], &chunkToBytes(header.chunk)); - mem.writeIntBig(u16, res[8..10], header.format); - mem.writeIntBig(u16, res[10..12], header.tracks); - mem.writeIntBig(u16, res[12..14], header.division); - return res; -} - -pub fn int(writer: anytype, i: u28) !void { - var tmp = i; - var is_first = true; - var buf: [4]u8 = undefined; - var fbs = io.fixedBufferStream(&buf).writer(); - - // TODO: Can we find a way to not encode this in reverse order and then flipping the bytes? - while (tmp != 0 or is_first) : (is_first = false) { - fbs.writeByte(@as(u7, @truncate(tmp)) | (@as(u8, 1 << 7) * @intFromBool(!is_first))) catch - unreachable; - tmp >>= 7; - } - mem.reverse(u8, fbs.context.getWritten()); - try writer.writeAll(fbs.context.getWritten()); -} - -pub fn metaEvent(writer: anytype, event: midi.file.MetaEvent) !void { - try writer.writeByte(event.kind_byte); - try int(writer, event.len); -} - -pub fn trackEvent(writer: anytype, last_event: ?midi.file.TrackEvent, event: midi.file.TrackEvent) !void { - const last_midi_event = if (last_event) |e| switch (e.kind) { - .MidiEvent => |m| m, - .MetaEvent => null, - } else null; - - try int(writer, event.delta_time); - switch (event.kind) { - .MetaEvent => |meta| { - try writer.writeByte(0xFF); - try metaEvent(writer, meta); - }, - .MidiEvent => |msg| try message(writer, last_midi_event, msg), - } -} - -pub fn file(writer: anytype, f: midi.File) !void { - try writer.writeAll(&encode.fileHeaderToBytes(.{ - .chunk = .{ - .kind = midi.file.Chunk.file_header.*, - .len = @as(u32, @intCast(midi.file.Header.size + f.header_data.len)), - }, - .format = f.format, - .tracks = @as(u16, @intCast(f.chunks.len)), - .division = f.division, - })); - try writer.writeAll(f.header_data); - - for (f.chunks) |c| { - try writer.writeAll(&encode.chunkToBytes(.{ - .kind = c.kind, - .len = @as(u32, @intCast(c.bytes.len)), - })); - try writer.writeAll(c.bytes); - } -} -``` - -- message 函数:这是将 MIDI 消息编码为字节序列的函数, 将单个 MIDI 消息编码为其二进制形式。根据消息类型,这会向提供的 writer 写入一个或多个字节。若消息需要状态字节,并且不同于前一个消息的状态,函数会写入状态字节。接着,根据消息的种类,函数会写入所需的数据字节。 - -- chunkToBytes 函数:将 MIDI 文件的块(Chunk)信息转换为 8 字节的二进制数据。这 8 字节中包括 4 字节的块类型和 4 字节的块长度。它复制块类型到前 4 个字节,然后写入块的长度到后 4 个字节,并返回结果。 - -- fileHeaderToBytes 函数:编码 MIDI 文件的头部为 14 字节的二进制数据。这 14 字节包括块信息、文件格式、轨道数量和时间划分信息。 - -- int 函数:将一个整数编码为 MIDI 文件中的可变长度整数格式。在 MIDI 文件中,某些整数值使用一种特殊的编码格式,可以根据整数的大小变化长度。 - -- metaEvent 函数:将 MIDI 元事件(Meta Event)编码为二进制数据, 这包括事件的类型和长度。具体则是编码一个元事件,首先写入其种类字节,然后是其长度。 - -- trackEvent 函数:编码轨道事件。轨道事件可以是元事件或 MIDI 事件,函数首先写入事件之间的时间差(delta 时间),然后根据事件类型(MetaEvent 或 MidiEvent)编码事件内容。 - -- file 函数:这是主函数,用于将整个 MIDI 文件数据结构编码为其二进制形式。它首先编码文件头,然后循环编码每个块和块中的事件。 - -### int 函数 - -```zig - -pub fn int(writer: anytype, i: u28) !void { - var tmp = i; - var is_first = true; - var buf: [4]u8 = undefined; - var fbs = io.fixedBufferStream(&buf).writer(); - - // TODO: Can we find a way to not encode this in reverse order and then flipping the bytes? - while (tmp != 0 or is_first) : (is_first = false) { - fbs.writeByte(@as(u7, @truncate(tmp)) | (@as(u8, 1 << 7) * @intFromBool(!is_first))) catch - unreachable; - tmp >>= 7; - } - mem.reverse(u8, fbs.context.getWritten()); - try writer.writeAll(fbs.context.getWritten()); -} -``` - -这个函数`int`用于编码一个整数为 MIDI 文件中的可变长度整数格式。在 MIDI 文件中,许多值(如 delta 时间)使用这种可变长度编码。 - -详细地解析这个函数的每一步: - -1. **参数定义**: - -- `writer`: 任意类型的写入对象,通常是一种流或缓冲区,可以向其写入数据。 -- `i`: 一个最多 28 位的无符号整数(`u28`),即要编码的值。 - -1. **局部变量初始化**: - -- `tmp`:作为输入整数的临时副本。 -- `is_first`:一个布尔值,用于指示当前处理的是否是整数的第一个字节。 -- `buf`: 定义一个 4 字节的缓冲区。因为最大的`u28`值需要 4 个字节的可变长度编码。 -- `fbs`:使用`io.fixedBufferStream`创建一个固定缓冲区的流,并获取它的写入器。 - -1. **循环进行可变长度编码**: - -- 循环条件是:直到`tmp`为 0 并且不是第一个字节。 -- `: (is_first = false)` 是一个后置条件,每次循环结束后都会执行。 -- `(@as(u8, 1 << 7) * @intFromBool(!is_first))` - - `1 << 7`: 这个操作是左移操作。数字 1 在二进制中表示为`0000 0001`。当你将它左移 7 位时,你得到`1000 0000`,这在十进制中等于 `128`。 - - `@intFromBool(!is_first)`: 这是将上一步得到的布尔值转换为整数。在许多编程语言中,true 通常被视为 1,false 被视为 0。在 Zig 中,这种转换不是隐式的,所以需要用`@intFromBool()`函数来进行转换 - - `@as(u8, 1 << 7)`: 这里是将数字 128(从 1 << 7 得到)显式地转换为一个 8 位无符号整数。 - - `(@as(u8, 1 << 7) * @intFromBool(!is_first))`: 将转换后的数字 128 与从布尔转换得到的整数(0 或 1)相乘。如果`is_first`为`true`(即这是第一个字节),那么整个表达式的值为 0。如果`is_first为false`(即这不是第一个字节),那么整个表达式的值为 128(`1000 0000` in 二进制)。 - - 这种结构在 MIDI 变长值的编码中很常见。MIDI 变长值的每个字节的最高位被用作“继续”位,指示是否有更多的字节跟随。如果最高位是 1,那么表示还有更多的字节;如果是 0,表示这是最后一个字节。 -- 在每次迭代中,它提取`tmp`的最后 7 位并将其编码为一个字节,最高位根据是否是第一个字节来设置(如果是第一个字节,则为 0,否则为 1)。 -- 然后,整数右移 7 位,以处理下一个字节。 -- 请注意,这种编码方式实际上是从低字节到高字节的反向方式,所以接下来需要翻转这些字节。 - -1. **翻转字节**: - -- 使用`mem.reverse`翻转在固定缓冲区流中编码的字节。这是因为我们是以反序编码它们的,现在我们要将它们放在正确的顺序。 - -1. **写入结果**: - -- 使用提供的`writer`将翻转后的字节写入到目标位置。 - -## file.zig - -主要目的是为了表示和处理 MIDI 文件的不同部分,以及提供了一个迭代器来遍历 MIDI 轨道的事件。 - -```zig -const midi = @import("../midi.zig"); -const std = @import("std"); -const decode = @import("./decode.zig"); - -const io = std.io; -const mem = std.mem; - -pub const Header = struct { - chunk: Chunk, - format: u16, - tracks: u16, - division: u16, - - pub const size = 6; -}; - -pub const Chunk = struct { - kind: [4]u8, - len: u32, - - pub const file_header = "MThd"; - pub const track_header = "MTrk"; -}; - -pub const MetaEvent = struct { - kind_byte: u8, - len: u28, - - pub fn kind(event: MetaEvent) Kind { - return switch (event.kind_byte) { - 0x00 => .SequenceNumber, - 0x01 => .TextEvent, - 0x02 => .CopyrightNotice, - 0x03 => .TrackName, - 0x04 => .InstrumentName, - 0x05 => .Luric, - 0x06 => .Marker, - 0x20 => .MidiChannelPrefix, - 0x2F => .EndOfTrack, - 0x51 => .SetTempo, - 0x54 => .SmpteOffset, - 0x58 => .TimeSignature, - 0x59 => .KeySignature, - 0x7F => .SequencerSpecificMetaEvent, - else => .Undefined, - }; - } - - pub const Kind = enum { - Undefined, - SequenceNumber, - TextEvent, - CopyrightNotice, - TrackName, - InstrumentName, - Luric, - Marker, - CuePoint, - MidiChannelPrefix, - EndOfTrack, - SetTempo, - SmpteOffset, - TimeSignature, - KeySignature, - SequencerSpecificMetaEvent, - }; -}; - -pub const TrackEvent = struct { - delta_time: u28, - kind: Kind, - - pub const Kind = union(enum) { - MidiEvent: midi.Message, - MetaEvent: MetaEvent, - }; -}; - -pub const File = struct { - format: u16, - division: u16, - header_data: []const u8 = &[_]u8{}, - chunks: []const FileChunk = &[_]FileChunk{}, - - pub const FileChunk = struct { - kind: [4]u8, - bytes: []const u8, - }; - - pub fn deinit(file: File, allocator: *mem.Allocator) void { - for (file.chunks) |chunk| - allocator.free(chunk.bytes); - allocator.free(file.chunks); - allocator.free(file.header_data); - } -}; - -pub const TrackIterator = struct { - stream: io.FixedBufferStream([]const u8), - last_event: ?TrackEvent = null, - - pub fn init(bytes: []const u8) TrackIterator { - return .{ .stream = io.fixedBufferStream(bytes) }; - } - - pub const Result = struct { - event: TrackEvent, - data: []const u8, - }; - - pub fn next(it: *TrackIterator) ?Result { - const s = it.stream.inStream(); - var event = decode.trackEvent(s, it.last_event) catch return null; - it.last_event = event; - - const start = it.stream.pos; - - var end: usize = switch (event.kind) { - .MetaEvent => |meta_event| blk: { - it.stream.pos += meta_event.len; - break :blk it.stream.pos; - }, - .MidiEvent => |midi_event| blk: { - if (midi_event.kind() == .ExclusiveStart) { - while ((try s.readByte()) != 0xF7) {} - break :blk it.stream.pos - 1; - } - break :blk it.stream.pos; - }, - }; - - return Result{ - .event = event, - .data = s.buffer[start..end], - }; - } -}; -``` - -1. **Header 结构**: - -- 表示 MIDI 文件的头部。 -- 包含一个块、格式、轨道数以及除法。 - -2. **Chunk 结构**: - -- 表示 MIDI 文件中的块,每个块有一个种类和长度。 -- 定义了文件头和轨道头的常量。 - -3. **MetaEvent 结构**: - -- 表示 MIDI 的元事件。 -- 它有一个种类字节和长度。 -- 有一个函数,根据种类字节返回事件的种类。 -- 定义了所有可能的元事件种类。 - -4. **TrackEvent 结构**: - -- 表示 MIDI 轨道中的事件。 -- 它有一个 delta 时间和种类。 -- 事件种类可以是 MIDI 事件或元事件。 - -5. **File 结构**: - -- 表示整个 MIDI 文件。 -- 它有格式、除法、头部数据和一系列块。 -- 定义了一个子结构 FileChunk,用于表示文件块的种类和字节数据。 -- 提供了一个清除方法来释放文件的资源。 - -6. **TrackIterator 结构**: - -- 是一个迭代器,用于遍历 MIDI 轨道的事件。 -- 它使用一个 FixedBufferStream 来读取事件。 -- 定义了一个 Result 结构来返回事件和关联的数据。 -- 提供了一个`next`方法来读取下一个事件。 - -## Build.zig - -buid.zig 是一个 Zig 构建脚本(build.zig),用于配置和驱动 Zig 的构建过程。 - -```zig -const builtin = @import("builtin"); -const std = @import("std"); - -const Builder = std.build.Builder; -const Mode = builtin.Mode; - -pub fn build(b: *Builder) void { - const test_all_step = b.step("test", "Run all tests in all modes."); - inline for (@typeInfo(std.builtin.Mode).Enum.fields) |field| { - const test_mode = @field(std.builtin.Mode, field.name); - const mode_str = @tagName(test_mode); - - const tests = b.addTest("midi.zig"); - tests.setBuildMode(test_mode); - tests.setNamePrefix(mode_str ++ " "); - - const test_step = b.step("test-" ++ mode_str, "Run all tests in " ++ mode_str ++ "."); - test_step.dependOn(&tests.step); - test_all_step.dependOn(test_step); - } - - const example_step = b.step("examples", "Build examples"); - inline for ([_][]const u8{ - "midi_file_to_text_stream", - }) |example_name| { - const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig"); - example.addPackagePath("midi", "midi.zig"); - example.install(); - example_step.dependOn(&example.step); - } - - const all_step = b.step("all", "Build everything and runs all tests"); - all_step.dependOn(test_all_step); - all_step.dependOn(example_step); - - b.default_step.dependOn(all_step); -} -``` - -这个 build 比较复杂,我们逐行来解析: - -```zig -const test_all_step = b.step("test", "Run all tests in all modes."); -``` - -- 使用 b.step()方法定义了一个名为 test 的步骤。描述是“在所有模式下运行所有测试”。 - -```zig -inline for (@typeInfo(std.builtin.Mode).Enum.fields) |field| {} -``` - -- Zig 有几种构建模式,例如 Debug、ReleaseSafe 等, 上面则是为每种构建模式生成测试. - - 这里,@typeInfo()函数获取了一个类型的元信息。std.builtin.Mode 是 Zig 中定义的构建模式的枚举。Enum.fields 获取了这个枚举的所有字段。 - -```zig -const example_step = b.step("examples", "Build examples"); -``` - -- 配置示例构建,为所有示例创建的构建步骤. - -```zig -const all_step = b.step("all", "Build everything and runs all tests"); -all_step.dependOn(test_all_step); -all_step.dependOn(example_step); - -b.default_step.dependOn(all_step); -``` - -- all_step 是一个汇总步骤,它依赖于之前定义的所有其他步骤。最后,b.default_step.dependOn(all_step);确保当你仅仅执行 zig build(没有指定步骤)时,all_step 会被执行。 diff --git a/content/post/2023-10-14-zig-version-manager.org b/content/post/2023-10-14-zig-version-manager.org deleted file mode 100644 index bfd49d7..0000000 --- a/content/post/2023-10-14-zig-version-manager.org +++ /dev/null @@ -1,92 +0,0 @@ -#+TITLE: Zig 多版本管理 -#+DATE: 2023-10-14T09:23:41+0800 -#+LASTMOD: 2024-08-18T12:04:00+0800 - -由于 Zig 还在快速开发迭代中,因此项目很有可能出现新版本 Zig 无法编译的情况,这时候一方面可以跟踪上游进展,看看是否有 workaround,另一个就是使用固定的版本来开发这个项目,显然这种方式更靠谱一些,因此这篇文章就来介绍一些管理多个 Zig 版本的方式。 - -* Zig version manager -现在 Zig 的版本管理工具主要有如下几个: -- [[https://github.com/marler8997/zigup][marler8997/zigup]] :: Download and manage zig compilers -- [[https://github.com/tristanisham/zvm][tristanisham/zvm]] :: Lets you easily install/upgrade between different versions of Zig -- [[https://github.com/hendriknielaender/zvm][hendriknielaender/zvm]] :: Fast and simple zig version manager - -他们工作方式类似,大致步骤: -1. 从 [[https://ziglang.org/download/index.json][index.json]] 解析所有版本,然后根据当前系统架构确定要安装的二进制, -2. 创建一个类似 =current= 软链用来表示当前的 Zig 安装目录, -3. 根据项目下 =.zig-version= 文件中指定的版本,修改 =current= 软链的指向,保证 =PATH= 中的 Zig 版本是正确的 - -相关实现: -- https://github.com/tristanisham/zvm/blob/639dca34b4b1dcbf881fd684020222d8161e4f7a/cli/use.go#L38 -- https://github.com/hendriknielaender/zvm/blob/e0b1bedf1aea58c1627a983a8ef7fd8857a2ee80/src/alias.zig#L13 - -* 通用版本管理工具 asdf -#+begin_quote -鉴于社区之前的 [[https://github.com/asdf-community/asdf-zig][asdf-zig]] 已经年久失修,zigcc 已经 fork 过来,对下载的软件包增加了 checksum 校验,防止出现中间人攻击,欢迎大家使用。 -- https://github.com/zigcc/asdf-zig -#+end_quote -多版本管理是个常见的需求,比如 Python 中的 [[https://github.com/pyenv/pyenv][pyenv]]、Ruby 中的 [[https://rvm.io/][rvm]],甚至还有一个通用的框架:[[https://asdf-vm.com/][asdf]],名字简单暴力,敲起来得心应手。安装方式可以参考官方文档的 [[https://asdf-vm.com/guide/getting-started.html][Getting Started]]。 - -它们的工作原理也都比较类似,通过修改 Shell 中的环境变量实现。在执行具体命令(比如:python、zig)时,会拦截到一个中转的工具(学名叫 [[https://en.wikipedia.org/wiki/Shim_(computing)][shims]],翻译为“垫片”),由它来确定要执行的版本(比如项目根目录的 =.python-version= ),然后将实际参数派发过去。 - -社区也有 Zig 的插件,安装也比较简单: -#+begin_src bash -asdf plugin-add zig https://github.com/asdf-community/asdf-zig.git -#+end_src -** 常用命令 -#+begin_src bash -# 列举所有可安装的版本 -asdf list-all zig - -asdf install zig -asdf uninstall zig -# 设置全局默认版本,会写到 $HOME/.tool-versions 文件 -asdf global zig -# 设置当前目录使用的版本,会写到 $(pwd)/.tool-versions 文件 -asdf local zig -#+end_src - -这里说明一点,可以使用 =asdf install zig master= 的方式来安装 master 分支的 Zig,但是由于 Zig 的 master 一直在变,因此本次安装的版本可以会滞后,一个简单的办法是先卸载,再重新安装。 - -如果想保留,可以手动把 master 目录改个名字,然后修改 zig 的 shims 让其识别到这个版本,之后就可以继续安装最新的 master 版本: -#+begin_src bash -$ tree -L 1 ~/.asdf/installs/zig -installs/zig -├──0.11.0 -├──0.12.0-dev.891+2254882eb -└──master - -$ cat ~/.asdf/shims/zig -#!/usr/bin/env bash -# asdf-plugin: zig 0.11.0 -# asdf-plugin: zig master -# asdf-plugin: zig 0.12.0-dev.891+2254882eb -exec /opt/homebrew/Cellar/asdf/0.13.1/libexec/bin/asdf exec "zig" "$@" # asdf_allow: ' asdf ' -#+end_src -上面的 =0.12.0-dev.891+2254882eb= 即是从 master 重命名出来的版本。下面这个脚本可以自动把 master 的版本固定下来: -#+begin_src bash -V=$(zig version) - -mv ~/.asdf/installs/zig/master ~/.asdf/installs/zig/${V} - -# 如果是 Linux,直接用 sed -i 就好了,不需要后面的引号,macOS 上需要 -sed -i '' "1a\\ -# asdf-plugin: zig ${V} -" ~/.asdf/shims/zig -#+end_src - -** 问题排查 -asdf 现在不支持 verbose 选项,因此安装过程中如果卡在某个地方,没法排查。一般来说,主要是下载 tar 包慢了,毕竟这取决于网络环境,可以这么改一下 [[https://github.com/asdf-community/asdf-zig/blob/51876973b89c5919bb20a3b7a7ce71990f7f6a5e/bin/install#L67][install 文件]]: -#+begin_src diff - echo "∗ Downloading and installing Zig..." -- curl --fail --silent --location --create-dirs --output "$source_path" "$download_url" -+ curl --fail --progress-bar --location --create-dirs --output "$source_path" "$download_url" - tar -xf "$source_path" -C "$install_path" --strip-components=1 -#+end_src - -这样就可以看到进度了: -#+begin_src bash -curl --fail --progress-bar --location --create-dirs --output \ - ~/.asdf/installs/zig/master/zig-macos-aarch64-master.tar.xz \ - https://ziglang.org/builds/zig-macos-aarch64-0.12.0-dev.1127+32bc07767.tar.xz -##################################### 27.8% -#+end_src diff --git a/content/post/2023-12-24-zig-build-explained-part1.md b/content/post/2023-12-24-zig-build-explained-part1.md deleted file mode 100644 index 000ed23..0000000 --- a/content/post/2023-12-24-zig-build-explained-part1.md +++ /dev/null @@ -1,335 +0,0 @@ ---- -title: zig 构建系统解析 - 第一部分 -author: Reco -date: "2023-12-24T19:15:02+0800" ---- - -> - 原文链接: https://zig.news/xq/zig-build-explained-part-1-59lf -> - API 适配到 Zig 0.11.0 版本 - -Zig 构建系统仍然缺少文档,对很多人来说,这是不使用它的致命理由。还有一些人经常寻找构建项目的秘诀,但也在与构建系统作斗争。 - -本系列试图深入介绍构建系统及其使用方法。 - -我们将从一个刚刚初始化的 Zig 项目开始,逐步深入到更复杂的项目。在此过程中,我们将学习如何使用库和软件包、添加 C 代码,甚至如何创建自己的构建步骤。 - -## 免责声明 - -由于我不会解释 Zig 语言的语法或语义,因此我希望你至少已经有了一些使用 Zig 的基本经验。我还将链接到标准库源代码中的几个要点,以便您了解所有这些内容的来源。我建议你阅读编译系统的源代码,因为如果你开始挖掘编译脚本中的函数,大部分内容都不言自明。所有功能都是在标准库中实现的,不存在隐藏的构建魔法。 - -## 开始 - -我们通过新建一个文件夹来创建一个新项目,并在该文件夹中调用 zig init-exe。 - -这将生成如下 build.zig 文件(我去掉了注释) - -```zig -const std = @import("std"); -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - }); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); - const unit_tests = b.addTest(.{ - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - }); - - const run_unit_tests = b.addRunArtifact(unit_tests); - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&run_unit_tests.step); -} -``` - -## 基础知识 - -构建系统的核心理念是,Zig 工具链将编译一个 Zig 程序 (build.zig),该程序将导出一个特殊的入口点(`pub fn build(b: *std.build.Builder) void`),当我们调用 `zig build` 时,该入口点将被调用。 - -然后,该函数将创建一个由 std.build.Step 节点组成的有向无环图,其中每个步骤都将执行构建过程的一部分。 - -每个步骤都有一组依赖关系,这些依赖关系需要在步骤本身完成之前完成。作为用户,我们可以通过调用 `zig build ${step-name}` 来调用某些已命名的步骤,或者使用其中一个预定义的步骤(例如 install)。 - -要创建这样一个步骤,我们需要调用 Builder.step - -```zig -const std = @import("std"); -pub fn build(b: *std.build.Builder) void { - const named_step = b.step("step-name", "This is what is shown in help"); - _ = named_step; -} -``` - -这将为我们创建一个新的步骤 step-name,当我们调用 `zig build --help` 时将显示该步骤: - -```bash -$ zig build --help -使用方法: zig build [steps] [options] - -Steps: -install (default) Copy build artifacts to prefix path -uninstall Remove build artifacts from prefix path -step-name This is what is shown in help - -General Options: -... -``` - -请注意,除了在 zig build --help 中添加一个小条目并允许我们调用 zig build step-name 之外,这个步骤仍然没有任何作用。 - -Step 遵循与 std.mem.Allocator 相同的接口模式,需要实现一个 make 函数。步骤创建时将调用该函数。对于我们在这里创建的步骤,该函数什么也不做。 - -现在,我们需要创建一个稍正式的 Zig 程序: - -## 编译 Zig 源代码 - -要使用编译系统编译可执行文件,编译器需要使用函数 Builder.addExecutable,它将为我们创建一个新的 LibExeObjStep。这个步骤实现是 zig build-exe、zig build-lib、zig build-obj 或 zig test 的便捷封装,具体取决于初始化方式。本文稍后将对此进行详细介绍。 - -现在,让我们创建一个步骤来编译我们的 src/main.zig 文件(之前由 zig init-exe 创建) - -```zig -const std = @import("std"); -pub fn build(b: *std.build.Builder) void { - const exe = b.addExecutable(.{.name = "fresh",.root_source_file = .{ .path = "src/main.zig" },}); - const compile_step = b.step("compile", "Compiles src/main.zig"); - compile_step.dependOn(&exe.step); -} -``` - -我们在这里添加了几行。首先,const exe = b.addExecutable 将创建一个新的 LibExeObjStep,将 src/main.zig 编译成一个名为 fresh 的文件(或 Windows 上的 fresh.exe)。 - -第二个添加的内容是 compile_step.dependOn(&exe.step);。这就是我们构建依赖关系图的方法,并声明当执行 `compile_step` 时,`exe` 步骤也需要执行。 - -你可以调用 zig build,然后再调用 zig build compile 来验证这一点。第一次调用不会做任何事情,但第二次调用会输出一些编译信息。 - -这将始终在当前机器的调试模式下编译,因此对于初学者来说,这可能就足够了。但如果你想开始发布你的项目,你可能需要启用交叉编译: - -## 交叉编译 - -交叉编译是通过设置程序的目标和编译模式来实现的 - -```zig -const std = @import("std"); -pub fn build(b: *std.build.Builder) void { - const exe = b.addExecutable(.{ - .name = "fresh", - .root_source_file = .{ .path = "src/main.zig" }, - .optimize = .ReleaseSafe, - }); - const compile_step = b.step("compile", "Compiles src/main.zig"); - compile_step.dependOn(&exe.step); -} -``` - -在这里,`.optimize = .ReleaseSafe`, 将向编译调用传递 -O ReleaseSafe。但是!LibExeObjStep.setTarget 需要一个 std.zig.CrossTarget 作为参数,而你通常希望这个参数是可配置的。 - -幸运的是,构建系统为此提供了两个方便的函数: - -- Builder.standardReleaseOptions -- Builder.standardTargetOptions - -使用这些函数,可以将编译模式和目标作为命令行选项: - -```zig -const std = @import("std"); - -pub fn build(b: *std.build.Builder) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "fresh", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - }); - const compile_step = b.step("compile", "Compiles src/main.zig"); - compile_step.dependOn(&exe.step); -} -``` - -现在,如果你调用 zig build --help 命令,就会在输出中看到以下部分,而之前这部分是空的: - -```plain -Project-Specific Options: --Dtarget=[string] The CPU architecture, OS, and ABI to build for --Dcpu=[string] Target CPU features to add or subtract --Doptimize=[enum] Prioritize performance, safety, or binary size (-O flag) - Supported Values: - Debug - ReleaseSafe - ReleaseFast - ReleaseSmall -``` - -前两个选项由 standardTargetOptions 添加,其他选项由 standardOptimizeOption 添加。现在,我们可以在调用构建脚本时使用这些选项: - -```plain -zig build -Dtarget=x86_64-windows-gnu -Dcpu=athlon_fx -zig build -Doptimize=ReleaseSafe -zig build -Doptimize=ReleaseSmall -``` - -可以看到,对于布尔选项,我们可以省略 =true,直接设置选项本身。 - -但我们仍然必须调用 zig build 编译,因为默认调用仍然没有任何作用。让我们改变一下! - -## 安装工件 - -要安装任何东西,我们必须让它依赖于构建器的安装步骤。该步骤是已创建的,可通过 Builder.getInstallStep() 访问。我们还需要创建一个新的 InstallArtifactStep,将我们的 exe 文件复制到安装目录(通常是 zig-out) - -```zig -const std = @import("std"); -pub fn build(b: *std.build.Builder) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "fresh", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - }); - const install_exe = b.addInstallArtifact(exe, .{}); - b.getInstallStep().dependOn(&install_exe.step); -} -``` - -这将做几件事: - -1. 创建一个新的 InstallArtifactStep,将 exe 的编译结果复制到 $prefix/bin 中。 -2. 由于 InstallArtifactStep(隐含地)依赖于 exe,因此它也将编译 exe -3. 当我们调用 zig build install(或简称 zig build)时,它将创建 InstallArtifactStep。 -4. InstallArtifactStep 会将 exe 的输出文件注册到一个列表中,以便再次卸载它 - -现在,当你调用 zig build 时,你会看到一个新的目录 zig-out 被创建了.看起来有点像这样: - -```plain -zig-out -└── bin - └── fresh -``` - -现在运行 ./zig-out/bin/fresh,就能看到这条信息: - -```plain -info: All your codebase are belong to us. -``` - -或者,你也可以通过调用 zig build uninstall 再次卸载。这将删除 zig build install 创建的所有文件,但不会删除目录! - -由于安装过程是一个非常普通的操作,它有快捷方法,以缩短代码。 - -```zig -const std = @import("std"); - -pub fn build(b: *std.build.Builder) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "fresh", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - }); - b.installArtifact(exe); -} -``` - -如果你在项目中内置了多个应用程序,你可能会想创建几个单独的安装步骤,并手动依赖它们,而不是直接调用 b.installArtifact(exe);,但通常这样做是正确的。 - -请注意,我们还可以使用 Builder.installFile(或其他,有很多变体)和 Builder.installDirectory 安装任何其他文件。 - -现在,从理解初始构建脚本到完全扩展,还缺少一个部分: - -## 运行已构建的应用程序 - -为了开发用户体验和一般便利性,从构建脚本中直接运行程序是非常实用的。这通常是通过运行步骤实现的,可以通过 zig build run 调用。 - -为此,我们需要一个 RunStep,它将执行我们能在系统上运行的任何可执行文件 - -```zig -const std = @import("std"); -pub fn build(b: *std.build.Builder) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "fresh", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - }); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -RunStep 有几个函数可以为执行进程的 argv 添加值: - -- addArg 将向 argv 添加一个字符串参数。 -- addArgs 将同时添加多个字符串参数 -- addArtifactArg 将向 argv 添加 LibExeObjStep 的结果文件 -- addFileSourceArg 会将其他步骤生成的任何文件添加到 argv。 - -请注意,第一个参数必须是我们要运行的可执行文件的路径。在本例中,我们要运行 exe 的编译输出。 - -现在,当我们调用 zig build run 时,我们将看到与自己运行已安装的 exe 相同的输出: - -```plain -info: All your codebase are belong to us. -``` - -请注意,这里有一个重要的区别: 使用 RunStep 时,我们从 ./zig-cache/.../fresh 而不是 zig-out/bin/fresh 运行可执行文件!如果你加载的文件相对于可执行路径,这一点可能很重要。 - -RunStep 的配置非常灵活,可以通过 stdin 向进程传递数据,也可以通过 stdout 和 stderr 验证输出。你还可以更改工作目录或环境变量。 - -对了,还有一件事: - -如果你想从 zig 编译命令行向进程传递参数,可以通过访问 Builder.args 来实现 - -```zig -const std = @import("std"); -pub fn build(b: *std.build.Builder) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "fresh", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - }); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -这样就可以在 cli 上的 -- 后面传递参数: - -```plain -zig build run -- -o foo.bin foo.asm -``` - -## 结论 - -本系列的第一章应该能让你完全理解本文开头的构建脚本,并能创建自己的构建脚本。 - -大多数项目甚至只需要编译、安装和运行一些 Zig 可执行文件,所以你就可以开始了! - -下一部分我将介绍如何构建 C 和 C++ 项目。 diff --git a/content/post/2023-12-28-zig-build-explained-part2.md b/content/post/2023-12-28-zig-build-explained-part2.md deleted file mode 100644 index 5683d53..0000000 --- a/content/post/2023-12-28-zig-build-explained-part2.md +++ /dev/null @@ -1,561 +0,0 @@ ---- -title: zig 构建系统解析 - 第二部分 -author: Reco -date: "2023-12-24T19:15:02+0800" ---- - -> - 原文链接: https://zig.news/xq/zig-build-explained-part-2-1850 -> - API 适配到 Zig 0.11.0 版本 - -## 注释 - -从现在起,我将只提供一个最精简的 build.zig,用来说明解决一个问题所需的步骤。如果你想了解如何将所有这些文件粘合到一个构建文件中,请阅读本系列[第一篇文章](/post/2023/12/24/zig-build-explained-part1/)。 - -## 在命令行上编译 C 代码 - -Zig 有两种编译 C 代码的方法,而且这两种很容易混淆。 - -### 使用 zig cc - -Zig 提供了 LLVM c 编译器 clang。第一种是 zig cc 或 zig c++,它是与 clang 接近 1:1 的前端。由于我们无法直接从 build.zig 访问这些功能(而且我们也不需要!),所以我将在快速的介绍这个主题。 - -如前所述,zig cc 是暴露的 clang 前端。您可以直接将 CC 变量设置为 zig cc,并使用 zig cc 代替 gcc 或 clang 来使用 Makefiles、CMake 或其他编译系统,这样您就可以在已有的项目中使用 Zig 的完整交叉编译体验。请注意,这只是理论上的说法,因为很多编译系统无法处理编译器名称中的空格。解决这一问题的办法是使用一个简单的封装脚本或工具,将所有参数转发给 zig cc。 - -假设我们有一个由 main.c 和 buffer.c 生成的项目,我们可以用下面的命令行来构建它: - -```plain -zig cc -o example buffer.c main.c -``` - -这将为我们创建一个名为 example 的可执行文件(在 Windows 系统中,应使用 example.exe 代替 example)。与普通的 clang 不同,Zig 默认会插入一个 -fsanitize=undefined,它将捕捉你使用的未定义行为。 - -如果不想使用,则必须通过 -fno-sanitize=undefined 或使用优化的发布模式(如 -O2)。 - -使用 zig cc 进行交叉编译与使用 Zig 本身一样简单: - -```plain -zig cc -o example.exe -target x86_64-windows-gnu buffer.c main.c -``` - -如你所见,只需向 -target 传递目标三元组,就能调用交叉编译。只需确保所有外部库都已准备好进行交叉编译即可! - -## 使用 zig build-exe 和其他工具 - -使用 Zig 工具链构建 C 项目的另一种方法与构建 Zig 项目的方法相同: - -```plain -zig build-exe -lc main.c buffer.c -``` - -这里的主要区别在于,必须明确传递 -lc 才能链接到 libc,而且可执行文件的名称将从传递的第一个文件中推导出。如果想使用不同的可执行文件名,可通过 --name example 再次获取示例文件。 - -交叉编译也是如此,只需通过 -target x86_64-windows-gnu 或其他目标三元组即可: - -```plain -zig build-exe -lc -target x86_64-windows-gnu main.c buffer.c -``` - -你会发现,使用这条编译命令,Zig 会自动在输出文件中附加 .exe 扩展名,并生成 .pdb 调试数据库。如果你在此处传递 --name example,输出文件也会有正确的 .exe 扩展名,所以你不必考虑这个问题。 - -## 用 build.zig 创建 C 代码 - -那么,我们如何用 build.zig 来构建上面的两个示例呢? - -首先,我们需要创建一个新的编译目标: - -```zig -// demo2.1 -const std = @import("std"); -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "example", - // 这块调试了很久。最后的结论是根本不要写 - // .root_source_file = .{ .path = undefined }, - .target = target, - .optimize = optimize, - }); - // 这块调试了很久。API变了不会写,着了很久的文档和看了很久的代码 - exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("main.c"), .flags = &.{} }); - exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("buffer.c"), .flags = &.{} }); - //exe.linkLibC(); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -然后,我们通过 addCSourceFile 添加两个 C 语言文件: - -```plain -exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("main.c"), .flags = &.{} }); -exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("buffer.c"), .flags = &.{} }); -``` - -第一个参数 addCSourceFile 是要添加的 C 或 C++ 文件的名称,第二个参数是该文件要使用的命令行选项列表。 - -请注意,我们向 addExecutable 传递的是空值,因为我们没有要编译的 Zig 源文件。 - -现在,调用 zig build 可以正常运行,并在 zig-out/bin 中生成一个可执行文件。很好,我们用 Zig 构建了第一个小 C 项目! - -如果你想跳过检查 C 代码中的未定义行为,就必须在调用时添加选项: - -```zig -exe.addCSourceFile(.{.file = std.build.LazyPath.relative("buffer.c"), .flags = &.{"-fno-sanitize=undefined"}}); -``` - -## 使用外部库 - -通常情况下,C 项目依赖于其他库,这些库通常预装在 Unix 系统中,或通过软件包管理器提供。 - -为了演示这一点,我们创建一个小工具,通过 curl 库下载文件,并将文件内容打印到标准输出: - -```c -#include -#include - -static size_t writeData(void *ptr, size_t size, size_t nmemb, FILE *stream) { - size_t written; - written = fwrite(ptr, size, nmemb, stream); - return written; -} - -int main(int argc, char ** argv) -{ - if(argc != 2) - return 1; - - char const * url = argv[1]; - CURL * curl = curl_easy_init(); - if (curl == NULL) - return 1; - - curl_easy_setopt(curl, CURLOPT_URL, url); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, stdout); - CURLcode res = curl_easy_perform(curl); - curl_easy_cleanup(curl); - - if(res != CURLE_OK) - return 1; - - return 0; -} -``` - -要编译这个程序,我们需要向编译器提供正确的参数,包括包含路径、库和其他参数。幸运的是,我们可以使用 Zig 内置的 pkg-config 集成: - -```zig - // demo2.2 -const std = @import("std"); -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "downloader", - .target = target, - .optimize = optimize, - }); - exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("download.c"), .flags = &.{} }); - exe.linkSystemLibrary("curl"); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -让我们创建程序,并通过 URL 调用它 - -```plain -zig build -./zig-out/bin/downloader https://mq32.de/public/ziggy.txt -``` - -## 配置路径 - -由于我们不能在交叉编译项目中使用 pkg-config,或者我们想使用预编译的专用库(如 BASS 音频库),因此我们需要配置包含路径和库路径。 - -这可以通过函数 addIncludePath 和 addLibraryPath 来完成: - -```zig -//demo 2.3 -const std = @import("std"); -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "example", - .target = target, - .optimize = optimize, - }); - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("bass-player.c"), - .flags = &.{} - }); - exe.linkLibC(); - // 还是一步步看源代码,找新的函数,addIncludeDir,addLibDir ->new function - exe.addIncludePath(std.build.LazyPath.relative("bass/linux")); - exe.addLibraryPath(std.build.LazyPath.relative("bass/linux/x64")); - exe.linkSystemLibrary("bass"); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -addIncludePath 和 addLibraryPath 都可以被多次调用,以向编译器添加多个路径。这些函数不仅会影响 C 代码,还会影响 Zig 代码,因此 @cImport 可以访问包含路径中的所有头文件。 - -## 每个文件的包含路径 - -因此,如果我们需要为每个 C 文件设置不同的包含路径,我们就需要用不同的方法来解决这个问题: -由于我们仍然可以通过 addCSourceFile 传递任何 C 编译器标志,因此我们也可以在这里手动设置包含目录。 - -```zig -//demo2.4 -const std = @import("std"); -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "example", - .target = target, - .optimize = optimize, - }); - exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("multi-main.c"), .flags = &.{} }); - exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("multi.c"), .flags = &.{ "-I", "inc1" } }); - exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("multi.c"), .flags = &.{ "-I", "inc2" } }); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -上面的示例非常简单,所以你可能会想为什么需要这样的东西。答案是,有些库的头文件名称非常通用,如 api.h 或 buffer.h,而您希望使用两个共享头文件名称的不同库。 - -## 构建 C++ 项目 - -到目前为止,我们只介绍了 C 文件,但构建 C++ 项目并不难。你仍然可以使用 addCSourceFile,但只需传递一个具有典型 C++ 文件扩展名的文件,如 cpp、cxx、c++ 或 cc: - -```zig -//demo2.5 -const std = @import("std"); -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "example", - .target = target, - .optimize = optimize, - }); - exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("main.c"), .flags = &.{} }); - exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("buffer.cc"), .flags = &.{} }); - exe.linkLibCpp(); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -如你所见,我们还需要调用 linkLibCpp,它将链接 Zig 附带的 c++ 标准库。 - -这就是构建 C++ 文件所需的全部知识,没有什么更神奇的了。 - -## 指定语言版本 - -试想一下,如果你创建了一个庞大的项目,其中的 C 或 C++ 文件有新有旧,而且可能是用不同的语言标准编写的。为此,我们可以使用编译器标志来传递 -std=c90 或 -std=c++98: - -```zig -//demo2.6 -const std = @import("std"); -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "example", - .target = target, - .optimize = optimize, - }); - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("main.c"), - .flags = &.{"-std=c90"} - }); - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("buffer.cc"), - .flags = &.{"-std=c++17"} - }); - exe.linkLibCpp(); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -## 条件编译 - -与 Zig 相比,C 和 C++ 的条件编译方式非常繁琐。由于缺乏惰性求值的功能,有时必须根据目标环境来包含/排除文件。你还必须提供宏定义来启用/禁用某些项目功能。 - -Zig 编译系统可以轻松处理这两种变体: - -```zig -//demo2.7 -const std = @import("std"); -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const use_platform_io = b.option(bool, "platform-io", "Uses the native api instead of the C wrapper") orelse true; - const exe = b.addExecutable(.{ - .name = "example", - .target = target, - .optimize = optimize, - }); - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("print-main.c"), - .flags = &.{} - }); - if (use_platform_io) { - exe.defineCMacro("USE_PLATFORM_IO", null); - if (exe.target.isWindows()) { - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("print-windows.c"), - .flags = &.{} - }); - - } else { - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("print-unix.c"), - .flags = &.{} - }); - } - } - exe.linkLibC(); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -通过 defineCMacro,我们可以定义自己的宏,就像使用 -D 编译器标志传递宏一样。第一个参数是宏名,第二个值是一个可选项,如果不为空,将设置宏的值。 - -有条件地包含文件就像使用 if 一样简单,你可以这样做。只要不根据你想在构建脚本中定义的任何约束条件调用 addCSourceFile 即可。只包含特定平台的文件?看看上面的脚本就知道了。根据系统时间包含文件?也许这不是个好主意,但还是有可能的! - -## 编译大型项目 - -由于大多数 C(更糟糕的是 C++)项目都有大量文件(SDL2 有 411 个 C 文件和 40 个 C++ 文件),我们必须找到一种更简单的方法来编译它们。调用 addCSourceFile 400 次并不能很好地扩展。 - -因此,我们可以做的第一个优化就是将 c 和 c++ 标志放入各自的变量中: - -```zig -//demo2.8 -const std = @import("std"); -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "example", - .target = target, - .optimize = optimize, - }); - const flags = .{ - "-Wall", - "-Wextra", - "-Werror=return-type", - }; - const cflags = flags ++ .{"-std=c99"}; - const cppflags = cflags ++ .{ - "-std=c++17", - "-stdlib=libc++", - "-fno-exceptions", - }; - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("main.c"), - .flags = &cflags, - }); - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("buffer.cc"), - .flags = &cppflags, - }); - exe.linkLibC(); - exe.linkLibCpp(); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -这样就可以在项目的不同组件和不同语言之间轻松共享标志。 - -addCSourceFile 还有一个变种,叫做 addCSourceFiles。它使用的不是文件名,而是可编译的所有源文件的文件名片段。这样,我们就可以收集某个文件夹中的所有文件: - -```zig -//demo2.9 -const std = @import("std"); -pub fn build(b: *std.build.Builder) !void { - var sources = std.ArrayList([]const u8).init(b.allocator); - // Search for all C/C++ files in `src` and add them - { - var dir = try std.fs.cwd().openIterableDir(".", .{ .access_sub_paths = true }); - - var walker = try dir.walk(b.allocator); - defer walker.deinit(); - - const allowed_exts = [_][]const u8{ ".c", ".cpp", ".cxx", ".c++", ".cc" }; - while (try walker.next()) |entry| { - const ext = std.fs.path.extension(entry.basename); - const include_file = for (allowed_exts) |e| { - if (std.mem.eql(u8, ext, e)) - break true; - } else false; - if (include_file) { - // we have to clone the path as walker.next() or walker.deinit() will override/kill it - try sources.append(b.dupe(entry.path)); - } - } - } - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "example", - .target = target, - .optimize = optimize, - }); - exe.addCSourceFiles(sources.items, &.{}); - exe.linkLibC(); - exe.linkLibCpp(); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -正如您所看到的,我们可以轻松搜索某个文件夹中的所有文件,匹配文件名并将它们添加到源代码集合中。然后,我们只需为每个文件集调用一次 addCSourceFiles,就可以大展身手了。 - -你可以制定很好的规则来匹配 exe.target 和文件夹名称,以便只包含通用文件和适合你的平台的文件。不过,这项工作留给读者自己去完成。 - -注意:其他构建系统会考虑文件名,而 Zig 系统不会!例如,在一个 qmake 项目中不能有两个名为 data.c 的文件!Zig 并不在乎,你可以添加任意多的同名文件,只要确保它们在不同的文件夹中就可以了 😏。 - -## 编译 Objective C - -我完全忘了!Zig 不仅支持编译 C 和 C++,还支持通过 clang 编译 Objective C! - -虽然不支持 C 或 C++,但至少在 macOS 上,你已经可以编译 Objective C 程序并添加框架了: - -```zig -//demo2.10 -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "example", - .target = target, - .optimize = optimize, - }); - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("main.m"), - .flags = &.{}, - }); - exe.linkFramework("Foundation"); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -在这里,链接 libc 是隐式的,因为添加框架会自动强制链接 libc。是不是很酷? - -## 混合使用 C 和 Zig 源代码 - -现在,是最后一章: 混合 C 代码和 Zig 代码! - -为此,我们只需将 addExecutable 中的第二个参数设置为文件名,然后点击编译! - -```zig -//demo2.11 -const std = @import("std"); -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "example", - .root_source_file = .{ .path = "main.zig" }, - .target = target, - .optimize = optimize, - }); - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("buffer.c"), - .flags = &.{}, - }); - exe.linkLibC(); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -这就是需要做的一切!是这样吗? - -实际上,有一种情况现在还没有得到很好的支持: -您应用程序的入口点现在必须在 Zig 代码中,因为根文件必须导出一个 pub fn main(...) ……。 -因此,如果你想将 C 项目中的代码移植到 Zig 中,你必须将 argc 和 argv 转发到你的 C 代码中,并将 C 代码中的 main 重命名为其他函数(例如 oldMain),然后在 Zig 中调用它。如果需要 argc 和 argv,可以通过 std.process.argsAlloc 获取。或者更好: 在 Zig 中重写你的入口点,然后从你的项目中移除一些 C 语言! - -## 结论 - -假设你只编译一个输出文件,那么现在你应该可以将几乎所有的 C/C++ 项目移植到 build.zig。 - -如果你需要一个以上的构建工件,例如共享库和可执行文件,你应该阅读下一篇文章,它将介绍如何在一个 build.zig 中组合多个项目,以创建便捷的构建体验。 - -敬请期待! diff --git a/content/post/2023-12-29-zig-build-explained-part3.md b/content/post/2023-12-29-zig-build-explained-part3.md deleted file mode 100644 index d3a4cff..0000000 --- a/content/post/2023-12-29-zig-build-explained-part3.md +++ /dev/null @@ -1,502 +0,0 @@ ---- -title: zig 构建系统解析 - 第三部分 -author: Reco -date: "2023-12-29T19:15:02+0800" ---- - -> - 原文链接: https://zig.news/xq/zig-build-explained-part-3-1ima -> - API 适配到 Zig 0.11.0 版本 - -从现在起,我将只提供一个最精简的 build.zig,用来说明解决一个问题所需的步骤。如果你想了解如何将所有这些文件粘合到一个构建文件中,请阅读本系列[第一篇文章](/post/2023/12/24/zig-build-explained-part1/)。 - -## 复合项目 - -有很多简单的项目只包含一个可执行文件。但是,一旦开始编写库,就必须对其进行测试,通常会编写一个或多个示例应用程序。当人们开始使用外部软件包、C 语言库、生成代码等时,复杂性也会随之上升。 - -本文试图涵盖所有这些用例,并将解释如何使用 build.zig 来编写多个程序和库。 - -## 软件包 - -译者:此处代码和说明,需要 zig build-exe --pkg-begin,但是在 0.11 已经失效。所以删除。 - -## 库 - -但 Zig 也知道库这个词。但我们不是已经讨论过外部库了吗? - -在 Zig 的世界里,库是一个预编译的静态或动态库,就像在 C/C++ 的世界里一样。库通常包含头文件(.h 或 .zig)和二进制文件(通常为 .a、.lib、.so 或 .dll)。 - -这种库的常见例子是 zlib 或 SDL。 - -与软件包相反,链接库的方式有两种 - -- (静态库)在命令行中传递文件名 -- (动态库)使用 -L 将库的文件夹添加到搜索路径中,然后使用 -l 进行实际链接。 - -在 Zig 中,我们需要导入库的头文件,如果头文件在 Zig 中,则使用包,如果是 C 语言头文件,则使用 @cImport。 - -## 工具 - -如果我们的项目越来越多,那么在构建过程中就需要使用工具。这些工具通常会完成以下任务: - -生成一些代码(如解析器生成器、序列化器或库头文件) -捆绑应用程序(例如生成 APK、捆绑应用程序……)。 -创建资产包 -... -有了 Zig,我们不仅能在构建过程中利用现有工具,还能为当前主机编译我们自己(甚至外部)的工具并运行它们。 - -但我们如何在 build.zig 中完成这些工作呢? - -## 添加软件包 - -添加软件包通常使用 LibExeObjStep 上的 addPackage 函数。该函数使用一个 std.build.Pkg 结构来描述软件包的外观: - -```zig -pub const Module = struct { - builder: *Build, - source_file: LazyPath, - dependencies: std.StringArrayHashMap(*Module), -}; -``` - -我们可以看到,它有 2 个成员: - -source_file 是定义软件包根文件的 FileSource。这通常只是指向文件的路径,如 vendor/zig-args/args.zig -dependencies 是该软件包所需的可选软件包片段。如果我们使用更复杂的软件包,这通常是必需的。 - -这是个人建议:我通常会在 build.zig 的顶部创建一个名为 pkgs 的结构/名称空间,看起来有点像这样: - -```zig -const args = b.createModule(.{ - .source_file = .{ .path = "libs/args/args.zig" }, - .dependencies = &.{}, -}); - -const interface = b.createModule(.{ - .source_file = .{ .path = "libs/interface.zig/interface.zig" }, - .dependencies = &.{}, -}); - -const lola = b.createModule(.{ - .source_file = .{ .path = "src/library/main.zig" }, - .dependencies = &.{}, -}); -const pkgs = .{ - .args = args, - - .interface = interface, - - .lola = lola, -}; -``` - -随后通过编译步骤 exe,把模块加入进来。函数 addModule 的第一个参数 name 是模块名称 - -```zig -exe.addModule("lola",pkgs.lola); -exe.addModule("args",pkgs.args); -``` - -## 添加库 - -添加库相对容易,但我们需要配置更多的路径。 - -注:在上一篇文章中,我们已经介绍了大部分内容,但现在还是让我们快速复习一遍: - -假设我们要将 libcurl 链接到我们的项目,因为我们要下载一些文件。 - -### 系统库 - -对于 unixoid 系统,我们通常可以使用系统软件包管理器来链接系统库。方法是调用 linkSystemLibrary,它会使用 pkg-config 自行找出所有路径: - -```zig -//demo 3.2 -const std = @import("std"); -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "example", - .root_source_file = .{ .path = "main.zig" }, - .target = target, - .optimize = optimize, - }); - exe.linkLibC(); - exe.linkSystemLibrary("curl"); - b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} -``` - -对于 Linux 系统,这是链接外部库的首选方式。 - -### 本地库 - -不过,您也可以链接您作为二进制文件提供商的库。为此,我们需要调用几个函数。首先,让我们来看看这样一个库是什么样子的: - -```plain -./vendor/libcurl -include -│ └── curl -│ ├── curl.h -│ ├── curlver.h -│ ├── easy.h -│ ├── mprintf.h -│ ├─── multi.h -│ ├── options.h -│ ├── stdcheaders.h -│ ├── system.h -│ ├── typecheck-gcc.h -│ └── urlapi.h -├── lib -│ ├── libcurl.a -│ ├── libcurl.so -│ └── ... -├─── bin -│ └── ... -└──share - └── ... -``` - -我们可以看到,vendor/libcurl/include 路径包含我们的头文件,vendor/libcurl/lib 文件夹包含一个静态库(libcurl.a)和一个共享/动态库(libcurl.so)。 - -### 动态链接 - -要链接 libcurl,我们需要先添加 include 路径,然后向 zig 提供库的前缀和库名:(todo 代码有待验证,因为 curl 可能需要自己编译自己生成 static lib) - -```zig -//demo 3.3 -const std = @import("std"); -pub fn build(b: *std.build.Builder) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = .{ .path = "main.zig" }, - .target = target, - .optimize = optimize, - }); - b.installArtifact(exe); - exe.linkLibC(); - exe.addIncludePath(.{ .path = "vendor/libcurl/include" }); - exe.addLibraryPath(.{ .path = "vendor/libcurl/lib" }); - exe.linkSystemLibraryName("curl"); -} -``` - -addIncludePath 将文件夹添加到搜索路径中,这样 Zig 就能找到 curl/curl.h 文件。注意,我们也可以在这里传递 "vendor/libcurl/include/curl",但你通常应该检查一下你的库到底想要什么。 - -addLibraryPath 对库文件也有同样的作用。这意味着 Zig 现在也会搜索 "vendor/libcurl/lib "文件夹中的库。 - -最后,linkSystemLibrary 会告诉 Zig 搜索名为 "curl "的库。如果你留心观察,就会发现上面列表中的文件名是 libcurl.so,而不是 curl.so。在 unixoid 系统中,库文件的前缀通常是 lib,这样就不会将其传递给系统。在 Windows 系统中,库文件的名字应该是 curl.lib 或类似的名字。 - -## 静态链接 - -当我们要静态链接一个库时,我们必须采取一些不同的方法: - -```zig -pub fn build(b: *std.build.Builder) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - }); - b.installArtifact(exe); - exe.linkLibC(); - exe.addIncludeDir("vendor/libcurl/include"); - exe.addObjectFile("vendor/libcurl/lib/libcurl.a"); - exe.addIncludePath(.{ .path = "vendor/libcurl/include" }); - exe.addLibraryPath(.{ .path = "vendor/libcurl/lib" }); -} -``` - -对 addIncludeDir 的调用没有改变,但我们突然不再调用带 link 的函数了?你可能已经知道了: 静态库实际上就是对象文件的集合。在 Windows 上,这一点也很相似,据说 MSVC 也使用了相同的工具集。 - -因此,静态库就像对象文件一样,通过 addObjectFile 传递给链接器,并由其解包。 - -注意:大多数静态库都有一些传递依赖关系。在我编译 libcurl 的例子中,就有 nghttp2、zstd、z 和 pthread,我们需要再次手动链接它们: - -```zig -// 示例片段 -pub fn build(b: *std.build.Builder) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - }); - b.installArtifact(exe); - exe.linkLibC(); - exe.addIncludePath(.{ .path = "vendor/libcurl/include" }); - exe.addLibraryPath(.{ .path = "vendor/libcurl/lib" }); - exe.linkSystemLibrary("nghttp2"); - exe.linkSystemLibrary("zstd"); - exe.linkSystemLibrary("z"); - exe.linkSystemLibrary("pthread"); -} -``` - -我们可以继续静态链接越来越多的库,并拉入完整的依赖关系树。 - -## 通过源代码链接库 - -不过,我们还有一种与 Zig 工具链截然不同的链接库方式: - -我们可以自己编译它们! - -这样做的好处是,我们可以更容易地交叉编译我们的程序。为此,我们需要将库的构建文件转换成我们的 build.zig。这通常需要对 build.zig 和你的库所使用的构建系统都有很好的了解。但让我们假设这个库是超级简单的,只是由一堆 C 文件组成: - -```zig -// 示例片段 -pub fn build(b: *std.build.Builder) void { - const cflags = .{}; - - const curl = b.addSharedLibrary("curl", null, .unversioned); - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("vendor/libcurl/src/tool_main.c"), - .flags = &cflags, - }); - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("vendor/libcurl/src/tool_msgs.c"), - .flags = &cflags, - }); - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("vendor/libcurl/src/tool_dirhie.c"), - .flags = &cflags, - }); - exe.addCSourceFile(.{ - .file = std.build.LazyPath.relative("vendor/libcurl/src/tool_doswin.c"), - .flags = &cflags, - }); - const target = b.standardTargetOptions(.{}); - exe.linkLibC(); - exe.addIncludePath(.{ .path = "vendor/libcurl/include" }); - exe.linkLibrary(curl); - b.installArtifact(exe); - -} -``` - -这样,我们就可以使用 addSharedLibrary 和 addStaticLibrary 向 LibExeObjStep 添加库。 - -这一点尤其方便,因为我们可以使用 setTarget 和 setBuildMode 从任何地方编译到任何地方。 - -## 使用工具 - -在工作流程中使用工具,通常是在需要以 bison、flex、protobuf 或其他形式进行预编译时。工具的其他用例包括将输出文件转换为不同格式(如固件映像)或捆绑最终应用程序。 - -系统工具 -使用预装的系统工具非常简单,只需使用 addSystemCommand 创建一个新步骤即可: - -```zig -// demo 3.5 -const std = @import("std"); -pub fn build(b: *std.build.Builder) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - }); - const cmd = b.addSystemCommand(&.{ - "flex", - "-outfile=lines.c", - "lines.l", - }); - b.installArtifact(exe); - exe.step.dependOn(&cmd.step); -} -``` - -从这里可以看出,我们只是向 addSystemCommand 传递了一个选项数组,该数组将反映我们的命令行调用。然后,我们按照习惯创建可执行文件,并使用 dependOn 在 cmd 上添加步骤依赖关系。 - -我们也可以反其道而行之,在编译程序时添加有关程序的小信息: - -```zig -//demo3.6 -const std = @import("std"); -pub fn build(b: *std.build.Builder) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = .{ .path = "main.zig" }, - .target = target, - .optimize = optimize, - }); - b.installArtifact(exe); - const cmd = b.addSystemCommand(&.{"size"}); - cmd.addArtifactArg(exe); - b.getInstallStep().dependOn(&cmd.step); -} -``` - -size 是一个很好的工具,它可以输出有关可执行文件代码大小的信息,可能如下所示: - -文本 数据 BSS Dec 十六进制 文件名 -12377 620 104 13101 332d ... - -如您所见,我们在这里使用了 addArtifactArg,因为 addSystemCommand 只会返回一个 std.build.RunStep。这样,我们就可以增量构建完整的命令行,包括任何 LibExeObjStep 输出、FileSource 或逐字参数。 - -## 全新工具 - -最酷的是 我们还可以从 LibExeObjStep 获取 std.build.RunStep: - -```zig -// 示例片段 -const std = @import("std"); -pub fn build(b: *std.build.Builder) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const game = b.addExecutable(.{ - .name = "game", - .root_source_file = .{ .path = "src/game.zig" }, - .target = target, - .optimize = optimize, - }); - b.installArtifact(game); - const pack_tool = b.addExecutable(.{ - .name = "pack", - .root_source_file = .{ .path = "tools/pack.zig" }, - .target = target, - .optimize = optimize, - }); - //译者改动:const precompilation = pack_tool.run(); // returns *RunStep - const precompilation = b.addRunArtifact(pack_tool); - precompilation.addArtifactArg(game); - precompilation.addArg("assets.zip"); - - const pack_step = b.step("pack", "Packs the game and assets together"); - pack_step.dependOn(&precompilation.step); -} -``` - -此构建脚本将首先编译一个名为 pack 的可执行文件。然后将以我们的游戏和 assets.zig 文件作为命令行参数调用该可执行文件。 - -调用 zig build pack 时,我们将运行 tools/pack.zig。这很酷,因为我们还可以从头开始编译所需的工具。为了获得最佳的开发体验,你甚至可以从源代码编译像 bison 这样的 "外部 "工具,这样就不会依赖系统了! - -## 将所有内容放在一起 - -一开始,所有这些都会让人望而生畏,但如果我们看一个更大的 build.zig 实例,就会发现一个好的构建文件结构会给我们带来很大帮助。 - -下面的编译脚本将编译一个虚构的工具,它可以通过 flex 生成的词法器解析输入文件,然后使用 curl 连接到服务器,并在那里传送一些文件。当我们调用 zig build deploy 时,项目将被打包成一个 zip 文件。正常的 zig 编译调用只会准备一个未打包的本地调试安装。 - -```zig -// 示例片段 -const std = @import("std"); -pub fn build(b: *std.build.Builder) void { - const mode = b.standardOptimizeOption(.{}); - // const mode = b.standardReleaseOptions(); - - const target = b.standardTargetOptions(.{}); - - // Generates the lex-based parser - const parser_gen = b.addSystemCommand(&[_][]const u8{ - "flex", - "--outfile=review-parser.c", - "review-parser.l", - }); - - // Our application - const exe = b.addExecutable(.{ - .name = "upload-review", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = mode, - }); - - { - exe.step.dependOn(&parser_gen.step); - - exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("review-parser.c"), .flags = &.{} }); - - // add zig-args to parse arguments - - const ap = b.createModule(.{ - .source_file = .{ .path = "vendor/zig-args/args.zig" }, - .dependencies = &.{}, - }); - exe.addModule("args-parser", ap); - - // add libcurl for uploading - exe.addIncludePath(std.build.LazyPath.relative("vendor/libcurl/include")); - exe.addObjectFile(std.build.LazyPath.relative("vendor/libcurl/lib/libcurl.a")); - - exe.linkLibC(); - b.installArtifact(exe); - // exe.install(); - } - - // Our test suite - const test_step = b.step("test", "Runs the test suite"); - const test_suite = b.addTest(.{ - .root_source_file = .{ .path = "src/tests.zig" }, - }); - - test_suite.step.dependOn(&parser_gen.step); - exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("review-parser.c"), .flags = &.{} }); - - // add libcurl for uploading - exe.addIncludePath(std.build.LazyPath.relative("vendor/libcurl/include")); - exe.addObjectFile(std.build.LazyPath.relative("vendor/libcurl/lib/libcurl.a")); - - test_suite.linkLibC(); - - test_step.dependOn(&test_suite.step); - - { - const deploy_step = b.step("deploy", "Creates an application bundle"); - - // compile the app bundler - const deploy_tool = b.addExecutable(.{ - .name = "deploy", - .root_source_file = .{ .path = "tools/deploy.zig" }, - .target = target, - .optimize = mode, - }); - - { - deploy_tool.linkLibC(); - deploy_tool.linkSystemLibrary("libzip"); - } - - const bundle_app = b.addRunArtifact(deploy_tool); - bundle_app.addArg("app-bundle.zip"); - bundle_app.addArtifactArg(exe); - bundle_app.addArg("resources/index.htm"); - bundle_app.addArg("resources/style.css"); - - deploy_step.dependOn(&bundle_app.step); - } -} -``` - -如你所见,代码量很大,但通过使用块,我们可以将构建脚本结构化为逻辑组。 - -如果你想知道为什么我们不为 deploy_tool 和 test_suite 设置目标: -两者都是为了在主机平台上运行,而不是在目标机器上。 -此外,deploy_tool 还设置了固定的编译模式,因为我们希望快速编译,即使我们编译的是应用程序的调试版本。 - -## 总结 - -看完这一大堆文字,你现在应该可以构建任何你想要的项目了。我们已经学会了如何编译 Zig 应用程序,如何为其添加任何类型的外部库,甚至如何为发布管理对应用程序进行后处理。 - -我们还可以通过少量的工作来构建 C 和 C++ 项目,并将它们部署到各个地方,而不仅仅是 Zig 项目。 - -即使我们混合使用项目、工具和其他一切。一个 build.zig 文件就能满足我们的需求。但很快你就会发现... 编译文件很快就会重复,而且有些软件包或库需要大量代码才能正确设置。 - -在下一篇文章中,我们将学习如何将 build.zig 文件模块化,如何为 Zig 创建方便的 sdks,甚至如何创建自己的构建步骤! - -一如既往,继续黑客之旅! diff --git a/content/post/2024-01-12-how-to-release-your-zig-applications.md b/content/post/2024-01-12-how-to-release-your-zig-applications.md deleted file mode 100644 index 017f9b7..0000000 --- a/content/post/2024-01-12-how-to-release-your-zig-applications.md +++ /dev/null @@ -1,180 +0,0 @@ ---- -title: 如何发布 Zig 应用程序 -author: Rui Chen -date: "2024-01-12T12:04:50-0500" ---- - -> - 原文链接: https://zig.news/kristoff/how-to-release-your-zig-applications-2h90 -> - API 适配到 Zig 0.12.0 版本 -> - 本文配套代码在[这里](https://github.com/zigcc/zigcc.github.io/tree/main/examples/20240112)找到 - -你刚用 Zig 写了一个应用程序,并希望其他人使用它。 -让用户方便使用的一种方式是为他们提供应用程序的预构建可执行文件。 -接下来,我将讨论一个好的发版流程所需要正确处理的两个主要事项。 - -# 为什么提供预构建的可执行文件? - -鉴于 C/C++ 依赖系统如何工作(或者说 _不工作_),对于某些 C/C++ 项目来说, -提供预编译好的的可执行文件几乎是必须的, -否则,普通人将陷入构建系统和配置系统的泥潭, -而这些系统的数量还要乘以项目的依赖数量。 -使用 Zig 的话就不应该这样,因为 Zig 构建系统(加上即将推出的 Zig 包管理器)将能够处理一切,这意味着大多数编写良好的应用程序应该只需运行 `zig build` 即可成功构建。 - -话虽如此,你的应用程序越受欢迎,用户就越不关心它是用哪种语言编写的。 -你的用户不想安装 Zig 并运行构建过程就能轻松使用应用程序(99%的情况下,稍后会讲到剩下的 1%), -因此最好还是预先构建你的应用程序。 - -# `zig build` vs `zig build-exe` - -在本文中,我们将看到如何为 Zig 项目制作、发布构建, -因此值得花一点时间来完全理解 Zig 构建系统和命令行之间的关系。 - -如果你有一个非常简单的 Zig 应用程序(例如,单个文件,无任何依赖), -构建项目最简单的方式是使用 `zig build-exe myapp.zig`, -这会在当前路径下创建一个可执行文件。 - -随着项目的增长,特别是开始有依赖之后,你可能想添加一个 `build.zig` 文件, -并开始用到 Zig 构建系统。一旦你开始这么做,你就可以完全控制命令行参数来影响构建过程。 - -你可以使用 `zig init-exe` 来了解基线 `build.zig` 文件的样子。 -请注意,文件中的每一行代码都是显示定义,从而影响 `zig build` 子命令的行为。 - -最后一点需要注意的是,尽管使用 `zig build` 和 `zig build-exe` 时命令行参数会有所不同, -但在构建 Zig 代码方面,两者是等价的。更具体地说,尽管 Zig 构建可以调用任意命令, -并做其他可能根本与 Zig 代码无关的事情,但在构建 Zig 代码方面, -`zig build` 所做的一切就是为 `build-exe` 准备命令行参数。 -这意味着,在编译 Zig 代码方面,`zig build`(假定 `build.zig` 中的代码是正确的) -和 `zig build-exe` 之间是一一对应关系。唯一的区别只是便利性。 - -# 构建模式 - -使用 `zig build` 或 `zig build-exe myapp.zig` 构建一个 Zig 项目时, -默认得到是一个调试构建的可执行文件。调试构建主要是为了开发方便,因而,通常被认为不适合发版。 -调试构建旨在牺牲运行性能(运行更慢)来提高构建速度(编译更快), -不久, Zig 编译器将通过引入增量编译和就地二进制补丁(in-place binary patching) -来让这种权衡变得更加明显。 - -Zig 目前有三种主要的发版构建模式:`ReleaseSafe`、`ReleaseFast` 和 `ReleaseSmall`。 - -`ReleaseSafe` 应被视为发版时使用的主要模式:尽管使用了优化, -但仍保留了某些安全检查(例如,溢出和数组越界), -在发布处理不可控输入源(如互联网)的软件时,这些开销是绝对值得的。 - -`ReleaseFast` 旨在用于性能是主要关注点的软件, -例如视频游戏。这种构建模式不仅禁用了上述安全检查, -而且为了进行更激进的优化,它还假设代码中不存在这类编程错误。 - -`ReleaseSmall` 类似于 `ReleaseFast`(即,没有安全检查), -但它不是优先考虑性能,而是尝试最小化可执行文件大小。 -例如,这是一种对于 WebAssembly 来说非常有意义的构建模式, -因为你希望可执行文件尽可能小,而沙箱运行环境已经提供了很多安全保障。 - -# 如何设置构建模式 - -使用 `zig build-exe` 时,你可以添加 `-O ReleaseSafe` -(或 `ReleaseFast`,或 `ReleaseSmall`)以获得相应的构建模式。 - -使用 `zig build` 时,取决于构建脚本的配置。默认构建脚本将包含以下代码行: - -```zig -// standardReleaseOptions 允许我们在运行 zig build 时,手动选择需要构建的目标平台和架构 -// 默认情况下为本机构建 -const target = b.standardTargetOptions(.{}); - -// standardOptimizeOption 允许我们在运行 zig build 时,手动选择构建模式 -// 默认情况下为 Debug -const optimize = b.standardOptimizeOption(.{}); - -// 标准构建一个可执行二进制程序的步骤 -const exe = b.addExecutable(.{ - .name = "zig", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, -}); -``` - -这是你在命令行中指定发布模式的方式:`zig build -Doptimize=ReleaseSafe`(或 -`-Doptimize=ReleaseFast`,或 `-Doptimize=ReleaseSmall`)。 - -# 选择正确的构建目标 - -现在,我们已经选择了正确的发版模式,是时候考虑构建目标了。 -显而易见,如果使用的平台和构建平台不相同时,需要指定相应的构建目标, -但即使只打算为同一平台发版,也还是需要注意。 - -为了方便起见,假定你用的是 Windows 10,并试图为使用 Windows 10 的朋友构建可执行文件。 -最想当然的方式是直接调用 `zig build` 或 `zig build-exe`(参见前文关于两个命令之间的差异与相似之处),然后将生成的可执行文件发送给你的朋友。 - -如果这样做,有时它会工作,但有时它会因`非法指令`(或类似错误)而崩溃。这到底发生了什么? - -# CPU 特性 - -在构建时如果不指定构建目标,Zig 将面向当前的机器进行构建优化, -这意味着将利用当前 CPU 支持的所有指令集。如果 CPU 支持 AVX 扩展, -那么 Zig 将使用它来执行 SIMD 操作。但不幸的是, -这也意味着,如果你朋友的 CPU 没有 AVX 扩展,那么应用程序将崩溃, -因为这个可执行文件确实包含非法指令。 - -解决这个问题最简单的方法是:始终在进行发布时指定一个构建目标。 -没错,如果你指定你想要为 `x86-64-linux` 构建, -Zig 将设定一个与该系列所有 CPU 完全兼容的基线 CPU。 - -如果你想微调指令集的选择,你可以查看 `zig build` 的 `-Dcpu` 和 `zig build-exe` 的 -`-mcpu`。我不会在这篇文章中更多地涉及这些细节。 - -实操中,下面的命令行将是你为 Arm macOS 发版时会用到的构建命令: - -```zig -$ zig build -Dtarget=aarch64-macos -$ zig build-exe myapp.zig -target aarch64-macos -``` - -请注意,目前在使用 `zig build` 时 `=` 是必需的, -而在使用 `build-exe` 时它不起作用(即你必须在 `-target` 及其值之间放一个空格)。 -希望这些怪异的地方在不久将来会得到清理。 - -其它一些相关的构建目标: - -```zig -x86-64-linux // uses gnu libc -x86-64-linux-gnu // uses glibc -x86-64-musl // uses musl libc -x86-64-windows // uses MingW headers -x86-64-windows-msvc // uses MSVC headers but they need to be present in your system -wasm32-freestanding // you will have to use build-obj since wasm modules are not full exes -``` - -你可以通过调用 `zig targets` 看到 Zig 支持的目标 CPU 和 -操作系统(以及 libc 和指令集)的完整列表。温馨提示:这是一个很长的列表。 - -最后,别忘了 `build.zig` 里的一切都必须明确定义,因此目标选项可以通过以下几行代码设置: - -```zig -// standardReleaseOptions 允许我们在运行 zig build 时,手动选择需要构建的目标平台和架构 -// 默认情况下为本机构建 -const target = b.standardTargetOptions(.{}); - -// standardOptimizeOption 允许我们在运行 zig build 时,手动选择构建模式 -// 默认情况下为 Debug -const optimize = b.standardOptimizeOption(.{}); - -// 标准构建一个可执行二进制程序的步骤 -const exe = b.addExecutable(.{ - .name = "zig", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, -}); -``` - -这也意味着如果你想添加其他限制或以某种方式改变构建时应该如何指定目标, -你可以通过添加自己的代码来实现。 - -# 结束语 - -现在你已经了解了在进行发布构建时需要确保正确的事项:选择一个发布优化模式并选择正确的构建目标, -包括为你正在构建的同一系统进行发版构建。 - -这最后一点的一个有趣含义是,对于你的一些用户(通常情况下为 1%,乐观估计), -从头开始构建程序实际上更为可取,以确保他们充分利用其 CPU 的能力。 diff --git a/content/post/2024-04-06-zig-cpp.md b/content/post/2024-04-06-zig-cpp.md deleted file mode 100644 index 103c939..0000000 --- a/content/post/2024-04-06-zig-cpp.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -title: "通过 Zig,学习 C++ 元编程" -author: Peng He -date: 2024-04-06T19:57:49+08:00 ---- - -尽管 Zig 社区宣称 Zig 语言是一个更好的 C (better C),但是我个人在学习 Zig 语言时经常会“触类旁通”C++。在这里列举一些例子来说明我的一些体会,可能会有一些不正确的地方,欢迎批评指正。 - -# “元能力” vs “元类型” - -在我看来,C++的增强方式是希望赋予语言一种“元能力”,能够让人重新发明新的类型,使得使用 C++的程序员使用自定义的类型,进行一种类似于“领域内语言”(DSL)编程。一个通常的说法就是 C++中任何类型定义都像是在模仿基本类型`int`。比如我们有一种类型 T,那么我们则需要定义 T 在以下几种使用场景的行为: - -```C++ -T x; //构造 -T x = y; //隐式拷贝构造 -T x{y}; //显示拷贝构造 -x = y; //x的类型是T,复制运算符重载,当然也有可能是移动运算符重载。 -//以及一大堆其他行为,比如析构等等。 -``` - -通过定义各种行为,程序员可以用 C++去模拟基础类型`int`,自定义的创造新类型。但是 Zig 却采取了另一条路,这里我觉得 Zig 的取舍挺有意思,即它剥夺了程序员定义新类型的能力,只遵循 C 的思路,即`struct`就是`struct`,他和`int`就是不一样的,没有必要通过各种运算符重载来制造一种“幻觉”,模拟`int`。相反,Zig 吸收现代语言中最有用的“元类型”,比如`slice`,`tuple`,`tagged union`等作为语言内置的基本类型,从这一点上对 C 进行增强。虽然这样降低了语言的表现力,但是却简化了设计,降低了“心智负担”。 - -比如 Zig 里的`tuple`,C++里也有`std::tuple`。当然,`std::tuple`是通过一系列的模板元编程的方式实现的,但是这个在 Zig 里是内置的,因此写代码时出现语法错误,Zig 可以直接告诉你是`tuple`用的不对,但是 C++则会打印很多错误日志。再比如`optional`,C++里也有`std::optinonal`,Zig 里只用`?T`。C++里有`std::variant`,而 Zig 里有`tagged union`。当然我们可以说,C++因为具备了这种元能力,当语法不足够“甜”时,我们可以发明新的轮子,但是代价就是系统愈发的复杂。而 Zig 则持续保持简单。 - -不过话说回来,很多底层系统的开发需求往往和这种类型系统的构建相悖,比如如果你的类型就是一个`int`的封装,那么即使发生拷贝你也无所谓性能开销。但是如果是一个`struct`,那么通常情况下,你会比较 care 拷贝,而可能考虑“移动”之类的手段。这个时候各种 C++的提供的幻觉,就成了程序员开发的绊脚石,经常你需要分析一段 C++表达式里到底有没有发生拷贝,他是左值还是右值,其实你在写 C 语言的时候也很少去考虑了这些,你在 Zig 里同样也不需要。 - -# 类型系统 - -C 语言最大弊病就是没有提供标准库,C++的标准库你要是能看懂,得具备相当的 C++的语法知识,但是 Zig 的标准库几乎不需要文档就能看懂。这其实是因为,在 C++里,类型不是一等成员(first class member),因此实现一些模版元编程算法特别不直观。但是在 Zig 里,`type`就是一等成员,比如你可以写: - -```zig -const x: type = u32; -``` - -即,把一个`type`当成一个变量使用。但是 C++里如何来实现这一行代码呢?其实是如下: - -```C++ -using x = uint32_t; -``` - -那么我们如果要对某个类型做个计算,比如组合一个新类型,Zig 里其实非常直观 - -```Zig -fn Some(comptime InputType: type) type -``` - -即输入一个类型,输出一个新类型,那么 C++里对应的东西是啥呢? - -```C++ -template -struct Some { - using OutputType = ... -} -``` - -相比之下, Zig 直观太多。那么很自然的,计算一个类型,Zig 里就是调用函数,而 C++则是模板类实例化,然后访问类成员。 - -```C++ -Some::OutputType -``` - -相当于对于 InputType 调用一个 Some“函数”,然后输出一个 OutputType。 - -# 命令式 VS 声明式 - -比如实现一个函数,输入一个 bool 值,根据 bool 值,如果为真,那么输出 type A,如果为假那么输出 type B。 - -```C++ -//基本模式 -template -struct Fn { - using OutputType = A; -}; - -//特例化的模式 -template -struct Fn { - using OutputType = B; -}; -``` - -从这里 C++代码可以感觉出,其实你是拿着尺子,对照着基础模式,然后通过模版偏特化来实现不同分支的逻辑。 - -```C++ -Fn sizeof(B), A, B>::OutputType -``` - -这段代码表面上看是声明了一个类型 OutputType,而这个类型的生成依赖于一些条件。而这些条件就是模板元编程,用来从 A 和 B 中选择类型大小更大的类型,如果想要表达更复杂的逻辑,则需要掌握更多模板的奇技淫巧。 - -如果用 Zig 来做,则要简单的多: - -```Zig -fn Fn(comptime A:type, comptime B: type) type { - if (@sizeOf(A) > @sizeOf(B)) { - return A; - } - return B; -} -``` - -这段代码和普通的 [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) 逻辑没什么区别,特殊的地方在于操作的对象是『类型』。 - -我们再来看递归的列子。比如有一个类型的 list,我们需要返回其中第 N 个 type。同样,由于在 C++中,类型不是一等成员,因此我们不可能有一个`vector`的东东。那怎么办呢?方法就是直接把`type list`放在模板的参数列表里:`typename ...T`。 - -于是,我们写出“函数原型”: - -```C++ -template -struct Fn; -``` - -然后我们递归的基础情况 - -```C++ -template -struct Fn<0, Head, Tail...> { - using Output = Head; -}; -``` - -然后写递归式, - -```C++ -template -struct Fn : public Fn -{ -}; -``` - -这个地方其实稍微有点难理解,其实就是拿着`...T`来模式匹配`Head, ...Tail`。 - -第一个偏特化,如果用命令式,类似于, - -```C++ -if (Index == 0) - return Head; -``` - -第二个偏特化,类似于 - -```C++ -else { - return Fn(Index-1, Tail...); -} -``` - -这里利用的其实是继承,让模板推导一路继承下去,如果 Index 不等于 0,那么`Fn`类其实是空类,即,我们无法继承到`using Output = ...`的这个`Output`。但是 index 总会等于 0,那么到了等于 0 的那天,递归就终止了,因为,我们不需要继续 Index - 1 下去了,编译器会选择特化好的`Fn<0, T,Tail...>`这个特化,而不会选择继续递归。 - -但是 Zig 实现这个也很直观,由于`slice`和`type`都是内置的,我们可以直接: - -```Zig -fn chooseN(N: u32, comptime type_list: []const type) type { - return type_list[N]; -} - -pub fn main() void { - const type_list = &[_]type{ u8, u16, u32, u64 }; - std.debug.print("{}\n", .{chooseN(2, type_list)}); -} -``` - -即这个也是完全命令式的。当然 C++20 之后也出现了`if constexpr`和`concept`来进一步简化模版元编程,C++的元编程也在向命令式的方向进化。 - -# 结束语 - -尽管 Zig 目前“还不成熟”,但是学习 Zig,如果采用一种对照的思路,偶尔也会“触类旁通”C++,达到举一反三的效果。 diff --git a/content/post/2024-05-07-package-hash.md b/content/post/2024-05-07-package-hash.md deleted file mode 100644 index a1ae544..0000000 --- a/content/post/2024-05-07-package-hash.md +++ /dev/null @@ -1,237 +0,0 @@ ---- -title: "build.zig.zon 中的依赖项哈希值" -author: Wenxuan Feng -date: 2024-05-07T02:45:10.692Z ---- - -> 原文地址:[build.zig.zon dependency hashes](https://zig.news/michalsieron/buildzigzon-dependency-hashes-47kj) - -# 引言 - -作者 Michał Sieroń 最近在思考 `build.zig.zon` 中的依赖项哈希值的问题。这些哈希值都有相同的前缀,而这对加密哈希函数来说极其不同寻常。习惯性使用 Conda 和 Yocto 对下载的压缩包运行 sha256sum,但生成的摘要与 `build.zig.zon` 中的哈希值完全不同。 - -```bash -.dependencies = .{ - .mach_freetype = .{ - .url = "https://pkg.machengine.org/mach-freetype/309be0cf11a2f617d06ee5c5bb1a88d5197d8b46.tar.gz", - .hash = "1220fcebb0b1a4561f9d116182e30d0a91d2a556dad8564c8f425fd729556dedc7cf", - .lazy = true, - }, - .font_assets = .{ - .url = "https://github.com/hexops/font-assets/archive/7977df057e855140a207de49039114f1ab8e6c2d.tar.gz", - .hash = "12208106eef051bc730bac17c2d10f7e42ea63b579b919480fec86b7c75a620c75d4", - .lazy = true, - }, - .mach_gpu_dawn = .{ - .url = "https://pkg.machengine.org/mach-gpu-dawn/cce4d19945ca6102162b0cbbc546648edb38dc41.tar.gz", - .hash = "1220a6e3f4772fed665bb5b1792cf5cff8ac51af42a57ad8d276e394ae19f310a92e", -} -``` - -以上摘录取自 [hexops/mach](https://github.com/hexops/mach/blob/bffc66800584123e2844c4bc4b577940806f9088/build.zig.zon#L13-L26) 项目。 - -# 初步探索 - -经过一番探索,我找到了一个文档:[doc/build.zig.zon.md](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/doc/build.zig.zon.md),似乎没有任何线索指向它。而文档中对哈希有段简短的描述。 - -- **哈希** -- 类型为字符串。 -- **[多重哈希](https://multiformats.io/multihash/)** - 该哈希值是基于一系列文件内容计算得出的,这些文件是在获取URL后并应用了路径规则后得到的。 - 这个字段是最重要的;一个包是的唯一性是由它的哈希值确定的,不同的 URL 可能对应同一个包。 - -## 多重哈希 - -在他们的网站上有一个很好的可视化展示,说明了这一过程: [多重哈希](https://multiformats.io/multihash/)。 - -![multihash 示意图](/images/zon-multihash.webp) - -因此 `build.zig.zon` 中的哈希字段不仅包含了摘要(digest),还包含了一些元数据(metadata)。但即使我们丢弃了头部信息,得到的结果仍与下载的 `tar` 包的 `sha256` 摘要不相符。而这就涉及到了包含规则的问题。 - -## 包含规则(inclusion rules) - -回到 [doc/build.zig.zon.md](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/doc/build.zig.zon.md) 文件,我们看到: - -> 这个计算的 hash 结果是在获取 URL 后,根据应用路径给出的包含规则,然后通过获得的文件目录内容计算出来。 - -那神秘的包含规则是什么呢?不幸的是,我又没找到这些内容的具体描述。唯一提到这些的地方是在 [ziglang/src/Package/Fetch.zig](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Fetch.zig#L1-L28) 文件的开头,但只能了解到无关文件被过滤后,哈希值是在剩余文件的基础上计算出来的结果。 - -幸好在代码中快速搜索后,我们找到了负责计算哈希的 `fetch` 任务的 [主函数](https://github.com/ziglang/zig/blob/9d64332a5959b4955fe1a1eac793b48932b4a8a8/src/main.zig#L6865)。 - -我们看到它调用了 [`runResource`](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Fetch.zig#L478-L485) 函数。路径字段从依赖的 `build.zig.zon` 中读取,并稍后用于创建某种过滤器。 - -这是我们一直在寻找的过滤器 `filter`。在这个结构的命名空间内定义了一个 `includePath` 函数,而它处理了所有那些包含规则。 - -```zig -/// sub_path is relative to the package root. -pub fn includePath(self: Filter, sub_path: []const u8) bool { - if (self.include_paths.count() == 0) return true; - if (self.include_paths.contains("")) return true; - if (self.include_paths.contains(".")) return true; - if (self.include_paths.contains(sub_path)) return true; - - // Check if any included paths are parent directories of sub_path. - var dirname = sub_path; - while (std.fs.path.dirname(dirname)) |next_dirname| { - if (self.include_paths.contains(next_dirname)) return true; - dirname = next_dirname; - } - - return false; -} -``` - -这个函数用于判断 `sub_path` 下的文件是否属于包的一部分。我们可以看到有三种特殊情况,文件会被认为是包的一部分: - -1. `include_paths` 为空 -2. `include_paths` 中含有空字符串 "" -3. `include_paths` 包含包的根目录 "." - -除此之外,这个函数会检查 `sub_path` 是否被明确列出,或者是已明确列出的目录的子目录。 - -## 计算哈希 - -现在我们知道了 `build.zig.zon` 的包含规则,也知道使用了 `SHA256` 算法。但我们仍然不知道实际的哈希结果是如何得到的。例如,它可能是通过将所有包含的文件内容输入哈希器来计算的。所以让我们再仔细看看,也许我们可以找到答案。 - -回到 `runResource` 函数,我们看到它调用了 [`computeHash`](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Fetch.zig#L1324) 函数,这看起来应该是我们感兴趣的主要内容(它顶部的注释已经无人维护,因为这里面会进行[文件删除](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Fetch.zig#L1383-L1385))。 - -在其中,我们偶然发现了这段[代码](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Fetch.zig#L1404-L1416): - -```zig -const hashed_file = try arena.create(HashedFile); -hashed_file.* = .{ - .fs_path = fs_path, - .normalized_path = try normalizePathAlloc(arena, entry_pkg_path), - .kind = kind, - .hash = undefined, // to be populated by the worker - .failure = undefined, // to be populated by the worker -}; -wait_group.start(); -try thread_pool.spawn(workerHashFile, .{ - root_dir, hashed_file, &wait_group, -}); -try all_files.append(hashed_file); -``` - -这里没有传递任何哈希对象,只传递了项目的根目录和一个指向 `HashedFile` 结构的指针。它有一个专门的 `hash` 字段。先前的猜想似乎不成立,因为哈希值是为单个文件存储的。为了更好地理解这个计算过程,顺着这条新线索看看后续。 - -跟踪 `workerHashFile`,我们看到它是 `hashFileFallible` 的一个简单包装,而后者看起来相当复杂。让我们来分解一下。 - -## 单个文件的哈希计算 - -首先,会进行一些初始化设置,其中创建并用规整后的文件路径初始化了一个新的哈希器实例: - -```zig -var buf: [8000]u8 = undefined; -var hasher = Manifest.Hash.init(.{}); -hasher.update(hashed_file.normalized_path); -``` - -然后我们根据我们正在哈希的文件类型进行切换。有两个分支: - -- 一个用于常规文件 -- 一个用于符号链接 - -首先来看看常规文件的情况: - -```zig -var file = try dir.openFile(hashed_file.fs_path, .{}); -defer file.close(); -// Hard-coded false executable bit: https://github.com/ziglang/zig/issues/17463 -hasher.update(&.{ 0, 0 }); -var file_header: FileHeader = .{}; -while (true) { - const bytes_read = try file.read(&buf); - if (bytes_read == 0) break; - hasher.update(buf[0..bytes_read]); - file_header.update(buf[0..bytes_read]); -} -``` - -首先,打开对应文件文件以便稍后读取其内容,这个符合预期,但紧接着我们放入了两个 null 字节。从阅读 [#17463](https://github.com/ziglang/zig/issues/17463) 来看,这似乎历史原因,为了进行历史兼容。无论如何,之后我们简单地循环读取文件数据的块,并将它们作为数据来计算哈希值。 - -现在来看看符号链接分支,这个更简单: - -```zig -const link_name = try dir.readLink(hashed_file.fs_path, &buf); -if (fs.path.sep != canonical_sep) { - // Package hashes are intended to be consistent across - // platforms which means we must normalize path separators - // inside symlinks. - normalizePath(link_name); -} -hasher.update(link_name); -``` - -首先进行路径分隔符的规整,保证不同平台一致,之后将符号链接的目标路径输入 `hasher`。在 `hashFileFallible` 函数最后,把计算出的哈希值赋值给 `HashedFile` 对象的 `hash` 字段。 - -## 组合哈希 - -尽管有了单个文件的哈希值,但我们仍不知道如何得到最终的哈希。幸运的是,曙光就在眼前。 - -下一步是确保我们有可复现的结果。 `HashedFile` 对象被存储在一个数组中,但文件系统遍历算法可能会改变,所以我们需要对那个数组进行排序。 - -```zig -std.mem.sortUnstable(*HashedFile, all_files.items, {}, HashedFile.lessThan); -``` - -最后,我们到达了将所有这些哈希[组合成一个的地方](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Fetch.zig#L1452-L1464): - -```zig -var hasher = Manifest.Hash.init(.{}); -var any_failures = false; -for (all_files.items) |hashed_file| { - hashed_file.failure catch |err| { - any_failures = true; - try eb.addRootErrorMessage(.{ - .msg = try eb.printString("unable to hash '{s}': {s}", .{ - hashed_file.fs_path, @errorName(err), - }), - }); - }; - hasher.update(&hashed_file.hash); -} -``` - -在这里我们看到所有计算出的哈希被一个接一个地输入到一个新的哈希器中。在 `computeHash` 的最后,我们返回 `hasher.finalResult()`,现在我们明白哈希值是如何获得的了。 - -## 最终多哈希值 - -现在我们有了一个 `SHA256` 摘要,可以最终返回到 `main.zig`,在那里我们调用 [`Package.Manifest.hexDigest(fetch.actual_hash)`](https://github.com/ziglang/zig/blob/9d64332a5959b4955fe1a1eac793b48932b4a8a8/src/Package/Manifest.zig#L174)。在那里,我们将多哈希头写入缓冲区,之后是我们的组合摘要。 - -顺便说一下,我们看到所有哈希头都是 `1220` 并非巧合。这是因为 `Zig` [硬编码了 SHA256](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Manifest.zig#L3) - 0x12,它有 32 字节的摘要 - 0x20。 - -# 总结 - -总结一下:最终哈希值是一个多哈希头 + `SHA256` 摘要。 - -这些摘要是包文件里的部分文件的 `SHA256` 摘要。这些摘要根据文件路径排序,并且对于普通文件和符号链接的计算方式不同。 - -这整个调查实际上是我尝试编写一个输出与 Zig 相同哈希的 shell 脚本的结果。如果你感兴趣,可以在这里阅读它:https://gist.github.com/michalsieron/a07da4216d6c5e69b32f2993c61af1b7。 - -在实验这个之后,我有一个想法,我很惊讶 Zig 没有检查 `build.zig.zon` 中列出的所有文件是否存在。但这可能是另一天的话题了。 - -# 译者注 - -在使用本地包时,可以使用下面的命令进行 hash 问题的排查: - -```bash - (main)$ zig fetch --debug-hash . -file: 001f530a93f06d8ad8339ec2f60f17ff9ff0ae29ceaed36942a8bc96ba9d7e26: LICENSE -file: ba267af6281ec7b52f90357cdf280e2bf974a0b493314705f18983d4fb818e90: build.zig -file: b2f7c2d2571a10f289112dbb16599ff88cc9709a7492fe42b76da92b9420ab18: build.zig.zon -file: 614020c9dc5abae8a2a0943030a4e1ddd1ab74a5b40e78896a9db24a353338e1: libs/.DS_Store -file: 673fd9dc257504fab812c8a7e79ec0cc90f83d426392dc8f1b990149db06e95f: libs/curl.zig -file: ebdf40a5c1308661cbaf1d69c3caf439f848e9a506029474f4e4f361e36fc836: libs/mbedtls.zig -file: e4e3a40d8e9670984f387936fcdeb9a2cbe86ee70ab898ba3837d922e5c14125: libs/zlib.zig -file: 6ba4206baa82168198e7c869ce01f002ee7e3cd67c200f5c603fa9c17201333f: src/Easy.zig -file: aabb5cedf021c6c7720103dd5e5a2088eeff36823a0ac3303fb965ca16012a8c: src/Multi.zig -file: 5f254a82524e9d625f7cf2ee80a601da642466d9e7ff764afad480529f51222a: src/errors.zig -file: a77c3ca16664533409c4618f54a43f9039427431894d09b03490a91a10864a4c: src/root.zig -file: 7b398ebd7ddb3ae30ff1ff1010445b3ed1f252db46608b6a6bd9aace233bc1a4: src/util.zig -1220110dc58ece4168ae3b2a0863c8676f8842bbbac763ad30e6ed1e2b68d973d615 -``` - -此外,社区已经有人把 multihash 的算法实现独立成一个单独的包,便于计算一个包的 hash 值: - -- https://github.com/Calder-Ty/multihash diff --git a/content/post/2024-05-24-interface-idioms.md b/content/post/2024-05-24-interface-idioms.md deleted file mode 100644 index aed8c33..0000000 --- a/content/post/2024-05-24-interface-idioms.md +++ /dev/null @@ -1,444 +0,0 @@ ---- -title: "Zig 标准库中的实现接口的惯用法与模式" -author: Rui Chen -date: 2024-05-24T23:21:12-05:00 ---- - -> 原文链接: https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj - -# 引言 - -在 Java 和 Go 中,可以使用“接口”(一组方法或方法集)定义基于行为的抽象。通常接口包含所谓的虚表(`vtable`) -以实现动态分派。Zig 允许在结构体、枚举、联合和不透明类型中声明函数和方法,尽管 Zig 尚未支持接口作为一种语言特性。 -Zig 标准库应用了一些代码习语或模式以达到类似效果。 - -类似于其他语言中的接口,Zig 的代码习语和模式实现了: - -- 在编译时对实例/对象方法与接口类型进行类型检查, -- 在运行时进行动态分派。 - -这里有一些显著的不同: - -- 在 Go 中,接口的定义与实现是独立的。可以在任何位置给一个类型实现新接口,只需保证其方法签名与新接口一致即可。无需像在 Java 中那样,需要回过头去修改类型定义,来实现新的接口。 -- Go 的接口只包含用于动态分派的 `vtab`,并且推荐 vtable 中方法即可能少 ,例如 `io.Reader` 和 `io.Writer`只有一个方法。 - 常见的工具函数如`io.Copy`、`CopyN`、`ReadFull`、`ReadAtLeast` 等,作为包函数提供,内部使用这些小接口。 - 与之相比,Zig 的接口,如 `std.mem.Allocator`,同时包含 `vtable` 和一些工具方法,因此方法会多一些。 - -以下是 Zig 的代码习语/模式在动态分派方面的学习笔记,代码摘自 Zig 标准库并以简单示例重录。为了专注于 vtab/动态分派,工具方法被移除, -代码稍作修改以适应 Go 中不依赖具体类型的“小”接口模式。 - -完整代码位于[此仓库](https://github.com/yglcode/zig_interfaces),你可以使用 `zig test interfaces.zig` 运行它。 - -# 背景设定 - -让我们使用经典的面向对象编程示例,创建一些形状:点(`Point`)、盒子(`Box`)和圆(`Circle`)。 - -```zig -const Point = struct { - x: i32 = 0, - y: i32 = 0, - pub fn move(self: *Point, dx: i32, dy: i32) void { - self.x += dx; - self.y += dy; - } - pub fn draw(self: *Point) void { - print("point@<{d},{d}>\n", .{ self.x, self.y }); - } -}; - -const Box = struct { - p1: Point, - p2: Point, - pub fn init(p1: Point, p2: Point) Box { - return .{ .p1 = p1, .p2 = p2 }; - } - pub fn move(self: *Box, dx: i32, dy: i32) void { - ...... - } - pub fn draw(self: *Box) void { - ...... - } -}; - -const Circle = struct { - center: Point, - radius: i32, - pub fn init(c: Point, r: i32) Circle { - return .{ .center = c, .radius = r }; - } - pub fn move(self: *Circle, dx: i32, dy: i32) void { - self.center.move(dx, dy); - } - pub fn draw(self: *Circle) void { - ...... - } -}; - -// 创建一组“形状”用于测试 -fn init_data() struct { point: Point, box: Box, circle: Circle } { - return .{ - .point = Point{}, - .box = Box.init(Point{}, Point{ .x = 2, .y = 3 }), - .circle = Circle.init(Point{}, 5), - }; -} -``` - -# 接口1:枚举标签联合 - -Loris Cro 在[“使用 Zig 0.10.0 轻松实现接口”](https://zig.news/kristoff/easy-interfaces-with-zig-0100-2hc5) -中介绍了使用枚举标签联合作为接口的方法。这是最简单的解决方案,尽管你必须在联合类型中显式列出所有“实现”该接口的变体类型。 - -```zig -const Shape1 = union(enum) { - point: *Point, - box: *Box, - circle: *Circle, - pub fn move(self: Shape1, dx: i32, dy: i32) void { - switch (self) { - inline else => |s| s.move(dx, dy), - } - } - pub fn draw(self: Shape1) void { - switch (self) { - inline else => |s| s.draw(), - } - } -}; -``` - -我们可以如下测试: - -```zig -test "union_as_intf" { - var data = init_data(); - var shapes = [_]Shape1{ - .{ .point = &data.point }, - .{ .box = &data.box }, - .{ .circle = &data.circle }, - }; - for (shapes) |s| { - s.move(11, 22); - s.draw(); - } -} -``` - -# 接口2:vtable 和动态分派的第一种实现 - -Zig 已从最初基于嵌入式 `vtab` 和 `#fieldParentPtr()` 的动态分派切换到基于“胖指针”接口的以下模式; -请查阅此文章了解更多细节[“Allocgate 将在 Zig 0.9 中到来...”](https://pithlessly.github.io/allocgate.html)。 - -接口 `std.mem.Allocator` 使用了这种模式,所有标准分配器,如 `std.heap.[ArenaAllocator, GeneralPurposeAllocator, ...]` 都有一个方法 `allocator() Allocator` 来暴露这个接口。 -以下代码稍作改动,将接口从实现中分离出来。 - -```zig -const Shape2 = struct { - // 定义接口字段: ptr,vtab - ptr: *anyopaque, //ptr to instance - vtab: *const VTab, //ptr to vtab - const VTab = struct { - draw: *const fn (ptr: *anyopaque) void, - move: *const fn (ptr: *anyopaque, dx: i32, dy: i32) void, - }; - - // 定义封装 vtable 调用的接口方法 - pub fn draw(self: Shape2) void { - self.vtab.draw(self.ptr); - } - pub fn move(self: Shape2, dx: i32, dy: i32) void { - self.vtab.move(self.ptr, dx, dy); - } - - // 将具体实现类型/对象转换为接口 - pub fn init(obj: anytype) Shape2 { - const Ptr = @TypeOf(obj); - const PtrInfo = @typeInfo(Ptr); - assert(PtrInfo == .Pointer); // 必须是指针 - assert(PtrInfo.Pointer.size == .One); // 必须是单项指针 - assert(@typeInfo(PtrInfo.Pointer.child) == .Struct); // 必须指向一个结构体 - const alignment = PtrInfo.Pointer.alignment; - const impl = struct { - fn draw(ptr: *anyopaque) void { - const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); - self.draw(); - } - fn move(ptr: *anyopaque, dx: i32, dy: i32) void { - const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); - self.move(dx, dy); - } - }; - return .{ - .ptr = obj, - .vtab = &.{ - .draw = impl.draw, - .move = impl.move, - }, - }; - } -}; -``` - -我们可以如下测试: - -```zig -test "vtab1_as_intf" { - var data = init_data(); - var shapes = [_]Shape2{ - Shape2.init(&data.point), - Shape2.init(&data.box), - Shape2.init(&data.circle), - }; - for (shapes) |s| { - s.move(11, 22); - s.draw(); - } -} -``` - -# 接口3:vtable 和动态分派的第二种实现 - -在上述第一种实现中,通过 `Shape2.init()` 将 `Box` “转换”为接口 `Shape2` 时,会对 `box` 实例进行类型检查, -以确保其实现了 `Shape2` 的方法(包括名称的匹配签名)。第二种实现中有两个变化: - -- `vtable` 内联在接口结构中(可能的缺点是,接口大小增加)。 -- 需要根据接口进行类型检查的方法被显式地作为函数指针传入,这可能允许传入不同的方法,只要它们具有相同的参数/返回类型。 - 例如,如果 `Box` 有额外的方法,`stopAt(i32,i32)` 或甚至 `scale(i32,i32)`,我们可以将它们替换为 `move()`。 - 接口 `std.rand.Random` 和所有 `std.rand.[Pcg, Sfc64, ...]` 使用这种模式。 - -```zig -const Shape3 = struct { - // 指向实例的 ptr - ptr: *anyopaque, - // 内联 vtable - drawFnPtr: *const fn (ptr: *anyopaque) void, - moveFnPtr: *const fn (ptr: *anyopaque, dx: i32, dy: i32) void, - - pub fn init( - obj: anytype, - comptime drawFn: fn (ptr: @TypeOf(obj)) void, - comptime moveFn: fn (ptr: @TypeOf(obj), dx: i32, dy: i32) void, - ) Shape3 { - const Ptr = @TypeOf(obj); - assert(@typeInfo(Ptr) == .Pointer); // 必须是指针 - assert(@typeInfo(Ptr).Pointer.size == .One); // 必须是单项指针 - assert(@typeInfo(@typeInfo(Ptr).Pointer.child) == .Struct); // 必须指向一个结构体 - const alignment = @typeInfo(Ptr).Pointer.alignment; - const impl = struct { - fn draw(ptr: *anyopaque) void { - const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); - drawFn(self); - } - fn move(ptr: *anyopaque, dx: i32, dy: i32) void { - const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); - moveFn(self, dx, dy); - } - }; - - return .{ - .ptr = obj, - .drawFnPtr = impl.draw, - .moveFnPtr = impl.move, - }; - } - - // 定义封装 vtable 函数指针的接口方法 - pub fn draw(self: Shape3) void { - self.drawFnPtr(self.ptr); - } - pub fn move(self: Shape3, dx: i32, dy: i32) void { - self.moveFnPtr(self.ptr, dx, dy); - } -}; -``` - -我们可以如下测试: - -```zig -test "vtab2_as_intf" { - var data = init_data(); - var shapes = [_]Shape3{ - Shape3.init(&data.point, Point.draw, Point.move), - Shape3.init(&data.box, Box.draw, Box.move), - Shape3.init(&data.circle, Circle.draw, Circle.move), - }; - for (shapes) |s| { - s.move(11, 22); - s.draw(); - } -} -``` - -# 接口4:使用嵌入式 vtab 和 @fieldParentPtr() 的原始动态分派 - -接口 `std.build.Step` 和所有构建步骤 `std.build.[RunStep, FmtStep, ...]` 仍然使用这种模式。 - -```zig -// 定义接口/vtab -const Shape4 = struct { - drawFn: *const fn (ptr: *Shape4) void, - moveFn: *const fn (ptr: *Shape4, dx: i32, dy: i32) void, - // 定义封装 vtab 函数的接口方法 - pub fn draw(self: *Shape4) void { - self.drawFn(self); - } - pub fn move(self: *Shape4, dx: i32, dy: i32) void { - self.moveFn(self, dx, dy); - } -}; - -// 嵌入 vtab 并将 vtab 函数定义为方法的封装 -const Circle4 = struct { - center: Point, - radius: i32, - shape: Shape4, // 嵌入 vtab - pub fn init(c: Point, r: i32) Circle4 { - // 定义接口封装函数 - const impl = struct { - pub fn draw(ptr: *Shape4) void { - const self = @fieldParentPtr(Circle4, "shape", ptr); - self.draw(); - } - pub fn move(ptr: *Shape4, dx: i32, dy: i32) void { - const self = @fieldParentPtr(Circle4, "shape", ptr); - self.move(dx, dy); - } - }; - return .{ - .center = c, - .radius = r, - .shape = .{ .moveFn = impl.move, .drawFn = impl.draw }, - }; - } - // 以下是方法 - pub fn move(self: *Circle4, dx: i32, dy: i32) void { - self.center.move(dx, dy); - } - pub fn draw(self: *Circle4) void { - print("circle@<{d},{d}>radius:{d}\n", .{ self.center.x, self.center.y, self.radius }); - } -}; - -// 在结构体上直接嵌入 vtab 并定义 vtab 函数 -const Box4 = struct { - p1: Point, - p2: Point, - shape: Shape4, // 嵌入 vtab - pub fn init(p1: Point, p2: Point) Box4 { - return .{ - .p1 = p1, - .p2 = p2, - .shape = .{ .moveFn = move, .drawFn = draw }, - }; - } - // 以下是 vtab 函数,不是方法 - pub fn move(ptr: *Shape4, dx: i32, dy: i32) void { - const self = @fieldParentPtr(Box4, "shape", ptr); - self.p1.move(dx, dy); - self.p2.move(dx, dy); - } - pub fn draw(ptr: *Shape4) void { - const self = @fieldParentPtr(Box4, "shape", ptr); - print("box@<{d},{d}>-<{d},{d}>\n", .{ self.p1.x, self.p1.y, self.p2.x, self.p2.y }); - } -}; -``` - -我们可以如下测试: - -```zig -test "vtab3_embedded_in_struct" { - var box = Box4.init(Point{}, Point{ .x = 2, .y = 3 }); - var circle = Circle4.init(Point{}, 5); - - var shapes = [_]*Shape4{ - &box.shape, - &circle.shape, - }; - for (shapes) |s| { - s.move(11, 22); - s.draw(); - } -} -``` - -# 接口5:编译时的泛型接口 - -所有上述接口都侧重于 `vtab` 和动态分派:接口值将隐藏其持有的具体值的类型。因此,你可以将这些接口值放入数组中并统一处理。 - -通过 Zig 的编译时计算,你可以定义泛型算法,它可以与提供代码函数体所需的方法或操作符的任何类型一起工作。例如, -我们可以定义一个泛型算法: - -```zig -fn update_graphics(shape: anytype, dx: i32, dy: i32) void { - shape.move(dx, dy); - shape.draw(); -} -``` - -如上所示,“shape”可以是任何类型,只要它提供 `move()` 和 `draw()` 方法。所有类型检查都发生在编译时,并且没有动态分派。 - -接下来,我们可以定义一个泛型接口,捕获泛型算法所需的方法;我们可以用它来适应具有不同方法名称的某些类型/实例到所需的 API。 - -接口 `std.io.[Reader, Writer]` 以及 `std.fifo` 和 `std.fs.File` 使用这种模式。 - -由于这些泛型接口没有擦除其持有的值的类型信息,它们是不同的类型。因此,你不能将它们放入数组中以统一处理。 - -```zig -pub fn Shape5( - comptime Pointer: type, - comptime drawFn: *const fn (ptr: Pointer) void, - comptime moveFn: *const fn (ptr: Pointer, dx: i32, dy: i32) void, -) type { - return struct { - ptr: Pointer, - const Self = @This(); - pub fn init(p: Pointer) Self { - return .{ .ptr = p }; - } - // 封装传入的函数/方法的接口方法 - pub fn draw(self: Self) void { - drawFn(self.ptr); - } - pub fn move(self: Self, dx: i32, dy: i32) void { - moveFn(self.ptr, dx, dy); - } - }; -} - -// 一种泛型算法使用鸭子类型/静态分派。 -// 注意:形状可以是提供 `move()`/`draw()` 的“任何类型” -fn update_graphics(shape: anytype, dx: i32, dy: i32) void { - shape.move(dx, dy); - shape.draw(); -} - -// 定义一个具有相似但不同方法的 `TextArea` -const TextArea = struct { - position: Point, - text: []const u8, - pub fn init(pos: Point, txt: []const u8) TextArea { - return .{ .position = pos, .text = txt }; - } - pub fn relocate(self: *TextArea, dx: i32, dy: i32) void { - self.position.move(dx, dy); - } - pub fn display(self: *TextArea) void { - print("text@<{d},{d}>:{s}\n", .{ self.position.x, self.position.y, self.text }); - } -}; -``` - -我们可以如下测试: - -```zig -test "generic_interface" { - var box = Box.init(Point{}, Point{ .x = 2, .y = 3 }); - // 将泛型算法直接应用于匹配类型 - update_graphics(&box, 11, 22); - var textarea = TextArea.init(Point{}, "hello zig!"); - // 使用泛型接口来适应不匹配的类型 - var drawText = Shape5(*TextArea, TextArea.display, TextArea.relocate).init(&textarea); - update_graphics(drawText, 4, 5); -} -``` diff --git a/content/post/2024-06-10-zig-hashmap-1.md b/content/post/2024-06-10-zig-hashmap-1.md deleted file mode 100644 index 87d7fc0..0000000 --- a/content/post/2024-06-10-zig-hashmap-1.md +++ /dev/null @@ -1,386 +0,0 @@ ---- -title: "HashMap 原理介绍上篇" -author: Wenxuan Feng -date: 2024-06-10T07:57:05.138Z ---- - -> 阅读这篇文章的前提是了解 [Zig 的范型实现](https://www.openmymind.net/learning_zig/generics/) - -如大多数哈希映射实现一样,Zig 的 `std.HashMap` 依赖于两个函数:`hash(key: K) u64` 和 `eql(key_a: K, key_b: K) bool`。其中,哈希函数接收一个键并返回一个无符号的64位整数作为哈希码。相同的关键字总是会返回相同的哈希码。然而,为了处理不同的键可能生成相同哈希码的情况(即碰撞),我们还需要 `eql` 函数来确定两个键是否相等。 - -这是一些标准做法,但Zig的实现有一些特定的细节值得关注。尤其是考虑到标准库中包含多种哈希映射类型以及文档似乎不完整且令人困惑这一点。具体来说,有六种哈希映射变体:`std.HashMap`, `std.HashMapUnmanaged`, `std.AutoHashMap`, `std.AutoHashMapUnmanaged`, `std.StringHashMap`, 和 `std.StringHashMapUnmanaged`。 - -`std.HashMapUnmanaged` 包含了实现的主要部分。其他五个都是对它的简单包装。由于这些变体通过一个名为“unmanaged”的字段进行包装,因此这五种类型的文档处理不清晰。 - -如果查看 `std.HashMap` 的 `put` 方法,会发现一个经常重复的应用模式: - -```Zig -pub fn put(self: *Self, key: K, value: V) Allocator.Error!void { - return self.unmanaged.putContext(self.allocator, key, value, self.ctx); -} -``` - -正如我所说,大部分繁重的工作都由 `std.HashMapUnmanaged` 完成,其他变体通过一个名为 `unmanaged` 的字段对其进行封装。 - -# Unmanaged - -在Zig标准库中随处可见的类型命名约定是 `unmanaged`。这种命名方式表明所涉及的类型不维护 `allocator`。任何需要分配内存的方法都会显式地将 `allocator` 作为参数传递。要实际看到这一点,可以考虑下面这个链表的例子: - -```Zig -pub fn LinkedList(comptime T: type) type { - return struct { - head: ?*Node = null, - allocator: Allocator, - - const Self = @This(); - - pub fn init(allocator: Allocator) Self { - return .{ - .allocator = allocator, - }; - } - - pub fn deinit(self: Self) void { - var node = self.head; - while (node) |n| { - node = n.next; - self.allocator.destroy(n); - } - } - - pub fn append(self: *Self, value: T) !void { - const node = try self.allocator.create(Node); - node.value = value; - const h = self.head orelse { - node.next = null; - self.head = node; - return; - }; - node.next = h; - self.head = node; - } - - pub const Node = struct { - value: T, - next: ?*Node, - }; - }; -} -``` - -我们的初始化函数接受并存储一个 `std.mem.Allocator`。这个分配器随后将在 append 和 deinit 操作中根据需要使用。这在 Zig 中是一个常见的模式。上述 `unmanaged` 版本只有细微的差别: - -```zig -pub fn LinkedListUnmanaged(comptime T: type) type { - return struct { - head: ?*Node = null, - - const Self = @This(); - - pub fn deinit(self: Self, allocator: Allocator) void { - var node = self.head; - while (node) |n| { - node = n.next; - allocator.destroy(n); - } - } - - pub fn append(self: *Self, allocator: Allocator, value: T) !void { - const node = try allocator.create(Node); - // .. same as above - } - - // Node is the same as above - pub const Node = struct {...} - }; -} -``` - -整体而言,代码已经是高质量的,上面的更改是细微优化的一部分。 -我们不再有一个 `allocator` 字段。`append` 和 `deinit` 函数都多了一个额外的参数:`allocator`。因为我们不再需要存储 `allocator`,我们能够仅用默认值初始化 `LinkedListUnmanaged(T)`(即 `head: ?*Node = null`),并且能够完全移除 `init` 函数。这不是未管理类型的要求,但这是常见的做法。要创建一个 `LinkedListUnmanaged(i32)`,你可以这样做: - -```zig -var ll = LinkedListUnmanaged(i32){}; -``` - -这看起来有点神秘,但这是标准的 Zig。`LinkedListUnmanaged(i32)` 返回一个类型,所以上面的做法和执行 `var user = User{}` 并依赖 `User` 的默认字段值没有区别。 - -你可能会好奇 `unmanaged` 类型有什么用?但在我们回答这个问题之前,让我们考虑一下提供我们的 LinkedList 的 `managed` 和 `unmanaged` 版本有多容易。我们保持我们的 `LinkedListUnmanaged` 如原样,并改变我们的 `LinkedList` 来包装它: - -```zig -pub fn LinkedList(comptime T: type) type { - return struct { - allocator: Allocator, - unmanaged: LinkedListUnmanaged(T), - - const Self = @This(); - - pub fn init(allocator: Allocator) Self { - return .{ - .unmanaged = .{}, - .allocator = allocator, - }; - } - - pub fn deinit(self: Self) void { - self.unmanaged.deinit(self.allocator); - } - - pub fn append(self: *Self, value: T) !void { - return self.unmanaged.append(self.allocator, value); - } - - pub const Node = LinkedListUnmanaged(T).Node; - }; -} -``` - -这种简单的组合方式,正如我们上面所见,与各种哈希映射类型包装 `std.HashMapUnmanaged` 的方式相同。 - -`unmanaged` 类型有几个好处。最重要的是它们更加明确。与知道像 `LinkList(T)` 这样的类型可能在某个时刻需要分配内存不同,未管理变体的明确 API 标识了进行分配/释放的特定方法。这可以帮助减少意外并为调用者提供更大的控制权。未管理类型的次要好处是它们通过不引用分配器节省了一些内存。一些应用可能需要存储成千上万甚至更多这样的结构,在这种情况下,这种节省可以累积起来。 - -为了简化,本文的其余部分不会再提到 `unmanaged`。我们看到关于 `StringHashMap` 或 `AutoHashMap` 或 `HashMap` 的任何内容同样适用于它们的 Unmanaged 变体。 - -# HashMap 与 AutoHashMap - -std.HashMap 是一个泛型类型,它接受两个类型参数:键的类型和值的类型。正如我们所见,哈希映射需要两个函数:hash 和 eql。这两个函数合起来被称为“上下文(context)”。这两个函数都作用于键,并且没有一个单一的 hash 或 eql 函数适用于所有类型。例如,对于整数键,eql 将是 `a_key == b_key`;而对于 `[]const u8` 键,我们希望使用 `std.mem.eql(u8, a_key, b_key)`。 - -当我们使用 std.HashMap 时,我们需要提供上下文(这两个函数)。我们不久后将讨论这一点,但现在我们可以依赖 std.AutoHashMap,它为我们自动生成这些函数。可能会让你惊讶的是,AutoHashMap 甚至可以为更复杂的键生成上下文。以下操作是有效的: -以下是修正后的代码: - -```zig -const std = @import("std"); - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator{}.init(); - const allocator = gpa.allocator(); - - var h = std.AutoHashMap(User, i32).init(allocator); - try h.put(User{ id = 3, state = .active }, 9001); - defer h.deinit(); - - const User = struct { - id: i32, - state: State, - - const State = enum { active, pending }; - }; -} - -const User = struct { - id: i32, - state: State, - login_ids: []i32, // You intended to use an array here instead of a slice. - ... -}; -``` - -修改后的代码中,我修正了 `User` 结构体内部的 `login_ids` 从切片(`[]T`)改为了数组 (`[N]T`)。在 Zig 中,使用数组可以避免与切片相关的不确定性和模糊性问题。 - -此外,我还优化了 `std.heap.GeneralPurposeAllocator` 的初始化方式。原本的 `.{}` 是不必要的,并且已经更新至更简洁的形式。 -你会被原谅,如果你认为 `StringHashMap(V)` 是 `AutoHashMap([], V)` 的别名。但正如我们刚看到的,`AutoHashMap` 不支持切片键。我们可以确认这一点。尝试运行: - -```zig -const std = @import("std"); - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - - var h = std.AutoHashMap([]const u8, i32).init(allocator); - try h.put("over", 9000); - defer h.deinit(); -} -``` - -得到下面的错误: - -> error: `std.auto_hash.autoHash` does not allow slices here (`[]const u8`) because the intent is unclear. Consider using `std.StringHashMap` for hashing the contents of `[]const u8`. Alternatively, consider using `std.auto_hash.hash` or providing your own hash function instead. - -正如我之前所说,问题不是切片不能被哈希或比较,而是有些情况下,切片只有在引用相同内存时才会被认为是相等的,而另一些情况下,两个切片如果它们的元素相同就会被认为是相等的。但是,对于字符串,大多数人期望“teg”无论存储在哪里都应该等于“teg”。 - -```zig -const std = @import("std"); - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - - const name1: []const u8 = &.{'T', 'e', 'g'}; - const name2 = try allocator.dupe(u8, name1); - - const eql1 = std.meta.eql(name1, name2); - const eql2 = std.mem.eql(u8, name1, name2); - std.debug.print("{any}\n{any}", .{eql1, eql2}); -} -``` - -上述程序打印“false”,然后打印“true”。`std.meta.eql`使用 `a.ptr == b.ptr` 和 `a.len == b.len` 来比较指针。但具体到字符串,大多数程序员可能期望 `std.mem.eql` 的行为,它比较字符串内部的字节。 - -因此,就像 `AutoHashMap` 包装了带有自动生成上下文的 `HashMap` 一样,`StringHashMap` 也包装了带有字符串特定上下文的 `HashMap`。我们将更仔细地看上下文,但这里是 `StringHashMap` 使用的上下文: - -```zig -pub const StringContext = struct { - pub fn hash(self: @This(), s: []const u8) u64 { - _ = self; - return std.hash.Wyhash.hash(0, s); - } - pub fn eql(self: @This(), a: []const u8, b: []const u8) bool { - _ = self; - return std.mem.eql(u8, a, b); - } -}; -``` - -# 自定义上下文 - -我们将在第一部分结束时,直接使用 `HashMap`,这意味着提供我们自己的上下文。我们将从一个简单的例子开始:为不区分大小写的 ASCII 字符串创建一个 `HashMap`。我们希望以下内容输出:`Goku = 9000`。请注意,虽然我们使用键 `GOKU` 进行插入,但我们使用“goku”进行获取: - -```zig -const std = @import("std"); - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - - var h = std.HashMap([]const u8, i32, CaseInsensitiveContext, std.hash_map.default_max_load_percentage).init(allocator); - defer h.deinit(); - try h.put("GOKU", 9000); - std.debug.print("Goku = {d}\n", .{h.get("goku").?}); -} -``` - -与只需要值类型的 `StringHashMap` 泛型或需要键和值类型的 `AutoHashMap` 不同,`HashMap` 需要键类型、值类型、上下文类型和填充因子。我们在此未涉及填充因子;在上面我们使用了 Zig 的默认填充因子(80%)。我们的兴趣点在于 `CaseInsensitiveContext` 类型及其实现: - -```zig -const CaseInsensitiveContext = struct { - pub fn hash(_: CaseInsensitiveContext, s: []const u8) u64 { - var key = s; - var buf: [64]u8 = undefined; - var h = std.hash.Wyhash.init(0); - while (key.len >= 64) { - const lower = std.ascii.lowerString(buf[0..], key[0..64]); - h.update(lower); - key = key[64..]; - } - - if (key.len > 0) { - const lower = std.ascii.lowerString(buf[0..key.len], key); - h.update(lower); - } - return h.final(); - } - - pub fn eql(_: CaseInsensitiveContext, a: []const u8, b: []const u8) bool { - return std.ascii.eqlIgnoreCase(a, b); - } -}; -``` - -这两个函数的第一个参数是上下文本身的实例。这允许更高级的模式,其中上下文可能有状态。但在许多情况下,它并未使用。 - -我们的 `eql` 函数使用现有的 `std.ascii.eqlIgnoreCase` 函数以不区分大小写的方式比较两个键。很直观。 - -我们的 `hash` 函数可以分为两部分。第一部分是将键转换为小写。如果我们希望“goku”和“GOKU”被视为相等,我们的哈希函数必须为两者返回相同的哈希码。 -我们以 64 字节为一批,以避免分配缓冲区来保存小写值。之所以能做到这一点,是因为我们的散列函数可以使用新字节进行更新(这很常见)。 - -这引出了第二部分,什么是 `std.hash.Wyhash`?当谈到哈希表的哈希算法时(不同于加密哈希算法),我们需要考虑一些属性,例如性能(每次操作哈希表都需要哈希键),均匀分布(如果我们的哈希函数返回 `u64`,那么一组随机输入应该在该范围内均匀分布)和碰撞抗性(不同的值可能会产生相同的哈希码,但发生的次数越少越好)。有许多算法,一些专门用于特定输入(例如短字符串),一些专为特定硬件设计。`WyHash` 是一种流行的选择,适用于许多输入和特征。你基本上将字节输入,一旦完成,就会得到一个 `u64`(或取决于版本的 `u32`)。 - -```zig -const std = @import("std"); - -pub fn main() !void { - { - const name = "Teg"; - - var h = std.hash.Wyhash.init(0); - h.update(name); - std.debug.print("{d}\n", .{h.final()}); - } - - { - const name = "Teg"; - const err = @intFromError(error.OutOfMemory); - - var h = std.hash.Wyhash.init(0); - h.update(name); - h.update(std.mem.asBytes(&err)); - std.debug.print("{d}\n", .{h.final()}); - } -} -``` - -这段代码输出: `17623169834704516898`,接着是 `7758855794693669122`。这些数字不应该有任何意义。目标只是展示如何将数据输入我们的哈希函数以生成哈希码。 - -让我们看另一个例子。假设我们有一个 `User`,我们希望用它作为哈希表中的键: - -```zig -const User = struct { - id: u32, - name: []const u8, -}; -``` - -我们不能使用 `AutoHashMap`,因为 `name` 不支持切片。示例如下: - -```zig -const std = @import("std"); - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - - var h = std.HashMap(User, i32, User.HashContext, std.hash_map.default_max_load_percentage).init(allocator); - defer h.deinit(); - try h.put(.{.id = 1, .name = "Teg"}, 100); - try h.put(.{.id = 2, .name = "Duncan"}, 200); - - std.debug.print("{d}\n", .{h.get(.{.id = 1, .name = "Teg"}).?}); - std.debug.print("{d}\n", .{h.get(.{.id = 2, .name = "Duncan"}).?}); -} - -const User = struct { - id: u32, - name: []const u8, - - pub const HashContext = struct { - pub fn hash(_: HashContext, u: User) u64 { - // TODO - } - - pub fn eql(_: HashContext, a: User, b: User) bool { - // TODO - } - }; -}; -``` - -我们需要实现 `hash` 和 `eql` 函数。`eql`,通常很直观: - -```zig -pub fn eql(_: HashContext, a: User, b: User) bool { - if (a.id != b.id) return false; - return std.mem.eql(u8, a.name, b.name); -} -``` - -如果你看过我们的其他哈希示例,你可能会想到它的实现: - -```zig -pub fn hash(_: HashContext, u: User) u64 { - var h = std.hash.Wyhash.init(0); - h.update(u.name); - h.update(std.mem.asBytes(&u.id)); - return h.final(); -} -``` - -插入这两个函数,以上示例应该可以工作。 - -# 结论 - -希望你现在对 Zig 的哈希表的实现以及如何在代码中利用它们有了更好的理解。在大多数情况下,`std.StringHashMap` 或 `std.AutoHashMap` 就足够了。但知道 `*Unmanaged` 变体的存在和目的,以及更通用的 `std.HashMap`,可能会派上用场。如果没有其他用途,现在文档和它们的实现应该更容易理解了。 - -在下一部分,我们将深入探讨哈希表的键和值,它们是如何存储和管理的。 - -> 原文地址: https://www.openmymind.net/Zigs-HashMap-Part-1/ diff --git a/content/post/2024-06-11-zig-hashmap-2.md b/content/post/2024-06-11-zig-hashmap-2.md deleted file mode 100644 index 22dbfe2..0000000 --- a/content/post/2024-06-11-zig-hashmap-2.md +++ /dev/null @@ -1,372 +0,0 @@ ---- -title: "HashMap 原理介绍下篇" -author: Wenxuan Feng -date: 2024-06-11T07:57:06.138Z ---- - -在[第一部分](/post/2024/06/10/zig-hashmap-1/)中,我们探讨了六种 `HashMap` 变体之间的关系以及每种变体为开发人员提供的不同功能。我们主要关注如何为各种数据类型定义和初始化 `HashMap`,并讨论了当 `StringHashMap` 或 `AutoHashMap` 不支持的类型时使用自定义 `hash` 和 `eql` 函数的重要性。在这篇文章中,我们将更深入地研究键和值的存储、访问方式以及我们在它们生命周期管理中的责任。 - -Zig 的哈希表内部采用两个切片结构:一个用于存放键(key),另一个用于存储对应的值(value)。通过应用哈希函数计算得到的哈希码被用来在这些数组中定位条目。从基础代码出发,比如: - -```zig -var lookup = std.StringHashMap(i32).init(allocator); -defer lookup.deinit(); - -try lookup.put("Goku", 9001); -try lookup.put("Paul", 1234); -``` - -这样的操作在哈希表中形成了一个类似如下的可视化表示: - -``` -keys: values: - -------- -------- - | Paul | | 1234 | @mod(hash("Paul"), 5) == 0 - -------- -------- - | | | | - -------- -------- - | | | | - -------- -------- - | Goku | | 9001 | @mod(hash("Goku"), 5) == 3 - -------- -------- - | | | | - -------- -------- -``` - -当我们使用模运算(如 `@mod`)将哈希码映射到数组中一个固定数量的槽位上时,我们就有了条目的理想位置。这里的"理想"是指哈希函数可能会为不同的键生成相同的哈希值;在计算时,通过数组大小进行取模有助于处理这种碰撞情况。然而,在忽略可能的冲突前提下,以上就是我们当前哈希表的基本视图。 - -一旦哈希表被填满到一定程度(如第一部分中提到,Zig 的默认填充因子为 80%),它就需要进行扩展来容纳更多值,同时保持常数时间性能的查找操作。哈希表的扩展过程类似于动态数组的扩容,我们分配一个新数组,并将原始数组中的值复制到新数组(通常会增加原数组大小的两倍作为简单算法)。然而,在处理哈希表时,简单的键值对复制是不够的。因为我们不能使用一种哈希方法如 `@mod(hash("Goku"), 5)` 并期望在另一个不同的哈希方法下找到相同的条目如 `@mod(hash("Goku"), 10)`(请注意,因为数组大小已增加,5 变成了 10)。 - -这个基本的可视化表示将贯穿本文大部分内容,并且不断强调条目的位置需要保持一致性和可预测性。即使哈希表需要在增长时从一个底层数组移动到另一个(即当填充因子达到一定阈值并要求扩大以容纳更多数据时),这一事实是我们将反复回顾的主题。 - -# 值管理 - -如果我们对上述代码片段进行扩展,并调用 `lookup.get("Paul")`,返回的值将是 `1234`。在处理像 `i32` 这样的原始类型时,很难直观地理解 `get` 方法和它的可选返回类型 `?i32` 或更通用的 `?V`(其中 `V` 表示任何值类型)之间的区别。考虑到这一点,让我们通过替换 `i32` 为一个封装了更多信息的 `User` 类型来展示这一概念: - -```zig -// 示例:如果 i32 被替换为一个 User 类型,则会涉及更复杂的数据结构和访问逻辑。 -``` - -在上述场景中,我们引入了一个新的类型 `User`,用于演示 `get` 方法返回可选值的概念。通过这种方式,我们可以直观地理解 `get` 和 `getPtr` 方法之间的区别,并根据实际需要选择合适的方法来处理不同的数据访问需求。 - -```zig -const std = @import("std"); - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - - var lookup = std.StringHashMap(User).init(allocator); - defer lookup.deinit(); - - try lookup.put("Goku", .{ - .id = 9000, - .name = "Goku", - .super = false, - }); - - var user = lookup.get("Goku").?; - - user.super = true; - std.debug.print("{any}\n", .{lookup.get("Goku").?.super}); -} - -const User = struct { - id: i32, - name: []const u8, - super: bool, -}; -``` - -即使我们设置了 `user.super = true`,在 `lookup` 中的 `User` 的值仍然是 `false`。这是因为在 Zig 中,赋值是通过复制完成的。如果我们保持代码不变,但将 `lookup.get` 改为 `lookup.getPtr`,它将起作用。我们仍然在做赋值,因此仍然在复制一个值,但我们复制的值是哈希表中 `User` 的地址,而不是 `user` 本身。 - -`getPtr` 允许我们获取哈希表中值的引用。如上所示,这具有行为意义;我们可以直接修改存储在哈希表中的值。这也具有性能意义,因为复制大值可能会很昂贵。但是考虑我们上面的可视化,并记住,随着哈希表的填满,值可能会重新定位。考虑到这一点,你能解释为什么这段代码会崩溃吗?: - -```zig -const std = @import("std"); - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - - // change the type, just to make it easier to write this snippet - // the same would happen with our above StringHashMap(User) - var lookup = std.AutoHashMap(usize, usize).init(allocator); - defer lookup.deinit(); - - try lookup.put(100, 100); - const first = lookup.getPtr(100).?; - - for (0..50) |i| { - try lookup.put(i, i); - } - first.* = 200; -} -``` - -如果 `first.* = 200;` 的语法让您感到困惑,那么我们在操作指针,并向其指定的地址写入一个值。这里的指针指向了值数组中某个索引的位置,因此这种语法实际上是在数组内部直接设置了一个值。问题在于,在我们的插入循环过程中,哈希表正在增长,导致底层键和值被重新分配并移动。`getPtr` 函数返回的指针不再有效。在撰写本文时,哈希表默认大小为 8,填充因子是 80%。如果我们在遍历范围 `0..5` 时运行代码一切正常,但当增加一次迭代至 `0..6`(即尝试访问 `array[6]`)时,由于增长操作导致崩溃。在常规使用场景中,此问题通常不构成问题;您不太可能在修改哈希表时持有对某个条目的引用。但是,理解这种情况的发生以及其原因将帮助我们更好地利用其他返回值和键指针的哈希表功能。 - -回到我们的 `User` 示例,如果我们将 `lookup` 的类型从 `std.StringHashMap(User)` 改为 `std.StringHashMap(*User)` 会怎样?最大的影响将是值的生命周期。使用原来的 `std.StringHashMap(User)`,我们可以说 `lookup` 拥有这些值——我们插入的用户嵌入在哈希表的值数组中。这使得生命周期管理变得容易,当我们 `deinit` 我们的 `lookup` 时,底层的键和值数组会被释放。 - -我们的 `User` 有一个 `name: []const u8` 字段。我们的示例使用字符串字面量,它在程序的生命周期中静态存在。然而,如果我们的 `name` 是动态分配的,我们必须显式地释放它。我们将在更详细地探讨指针值时涵盖这一点。 - -使用 `*User` 打破了这种所有权。我们的哈希表存储指针,但它不拥有指针所指向的内容。尽管调用了 `lookup.deinit`,这段代码会导致用户泄漏: - -```zig -const std = @import("std"); - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - - var lookup = std.StringHashMap(*User).init(allocator); - defer lookup.deinit(); - - const goku = try allocator.create(User); - goku.* = .{ - .id = 9000, - .name = "Goku", - .super = false, - }; - try lookup.put("Goku", &goku.address); -} - -const User = struct { - id: i32, - name: []const u8, - super: bool, -}; -``` - -让我们将其可视化: - -``` -lookup - =============================== - ║ keys: values: ║ - ║ -------- ------------- ║ - ║ | Goku* | | 1024d4000 | ----> ------------- - ║ -------- ------------- ║ | 9000 | - ║ | | | | ║ ------------- - ║ -------- ------------- ║ | 1047300e4 |---> ----------------- - =============================== ------------- | G | o | k | u | - | 4 | ----------------- - ------------- - | false | - ------------- -``` - -我们将会在下一节讨论键,现在为了简单起见我们使用“Goku”。 - -双线框是我们的 `lookup`,表示它拥有并负责的内存。我们放入哈希表的引用将指向框外的值。这有许多含义。最重要的是,这意味着值的生命周期与哈希表的生命周期分离,调用 `lookup.deinit` 不会释放它们。 - -有一种常见情况是我们想使用指针并将值的生命周期与哈希表相关联。回想我们崩溃的程序,当对哈希表值的指针变得无效时。正如我所说,这通常不是问题,但在更高级的场景中,你可能希望不同部分的代码引用也存在于哈希表中的值。让我们重新审视上面的可视化,并思考如果我们的哈希表增长并重新定位键和值数组会发生什么: - -```zig -lookup - =============================== - ║ keys: values: ║ - ║ -------- ------------- ║ - ║ | | | | ║ - ║ -------- ------------- ║ - ║ -------- ------------- ║ - ║ | | | | ║ - ║ -------- ------------- ║ - ║ -------- ------------- ║ - ║ | Goku* | | 1024d4000 | ----> ------------- - ║ -------- ------------- ║ | 9000 | - ║ | | | | ║ ------------- - ║ -------- ------------- ║ | 1047300e4 |---> ----------------- - =============================== ------------- | G | o | k | u | - | 4 | ----------------- - ------------- - | false | - ------------- -``` - -这两个数组已经增长、重新分配,并且我们的条目索引已重新计算,但我们实际的 `User`(也就是 `Goku`)仍然驻留在堆中的同一位置(内存位置 1047300e4)。就像 `deinit` 不会改变双线框外的任何内容一样,其他变化(如增长)也不会改变它们。 - -一般来说,你是否应该存储值或指向值的指针将是显而易见的。这主要是因为像 `getPtr` 这样的方法使我们能够直接从哈希表中高效地检索和修改值。无论哪种方式,我们都可以获得性能上的好处,所以性能不是主要考虑因素。重要的是值是否需要比哈希表存活更久和/或在哈希表发生变化时对值的引用是否需要存在(并因此保持有效)。 - -在哈希表和引用的值应该链接的情况下,我们需要在调用 `lookup.deinit` 之前遍历这些值并清理它们: - -```zig -defer { - var it = lookup.valueIterator(); - while (it.next()) |value_ptr| { - allocator.destroy(value_ptr.*); - } - lookup.deinit(); -} -``` - -如果解引用 (`value_ptr.*`) 看起来不对劲,请回到可视化。我们的 `valueIterator` 给我们数组中值的指针,而数组中的值是 `*User`。因此,`value_ptr` 是 `**User`。 - -无论我们是存储 `User` 还是 `*User`,值中任何已分配的字段始终是我们的责任。在一个真实的应用程序中,你的用户名称不会是字符串字面量,它们会是动态分配的。在这种情况下,我们上面的 while 循环需要改为: - -```zig -while (it.next()) |value_ptr| { - const user = value_ptr.*; - allocator.free(user.name); - allocator.destroy(user); -} -``` - -即使我们的值是 `User`,其字段也是我们的责任(认为 `lookup.deinit` 会知道如何/需要释放什么有点荒谬): - -```zig -while (it.next()) |value_ptr| { - allocator.free(value_ptr.name); -} -``` - -在最后一种情况下,由于我们存储的是 `User` 而不是 `*User`,我们的 `value_ptr` 是指向 `User` 的指针(不像之前那样是指向指针的指针)。 - -# Keys - -我们可以开始和结束这一节:我们关于值的所有内容同样适用于键。这是100%正确的,但这在某种程度上不太直观。大多数开发人员很快就能理解,存储在哈希表中的堆分配的 `User` 实例有其自身的生命周期,需要显式管理/释放。但由于某些原因,这对于键来说并不那么明显。 - -像值一样,如果我们的键是原始类型(例如整数),我们不必做任何特别的事情。原始类型的键直接存储在哈希表的键数组中,因此其生命周期和内存与哈希表绑定。这是一种非常常见的情况。但另一种常见情况是使用 `std.StringHashMap` 的字符串键。这常常让刚接触 Zig 的开发人员感到困惑,但你需要保证字符串键在哈希表使用它们期间始终有效。而且,如果这些键是动态分配的,你需要确保在不再使用时释放它们。这意味着对键进行与值相同的处理。 - -让我们再次可视化我们的哈希表,但这次正确表示一个字符串键: - -``` -lookup - =================================== - ║ keys: values: ║ - ║ ------------- ------------ ║ - ║ | 1047300e4 | | 1024d4000 | ----> ------------- - ║ ------------- ------------- ║ | 9000 | - ║ | | | | ║ ------------- - ║ ------------- ------------- ║ | 1047300e4 |---> ----------------- - =================================== ------------- | G | o | k | u | - | 4 | ----------------- - ------------- - | false | - ------------- -``` - -在这个例子中,我们的键实际上是 `user.name`。将键作为值的一部分是非常常见的。这里是它可能的样子: - -```zig -const user = try allocator.create(User); -user.* = .{ - .id = 9000, - .super = false, - // 模拟来自动态源(如数据库)的名称 - .name = try allocator.dupe(u8, "Goku"), -}; -try lookup.put(user.name, user); -``` - -在这种情况下,我们之前的清理代码是足够的,因为我们已经在释放作为我们键的 `user.name`: - -```zig -defer { - var it = lookup.iterator(); - while (it.next()) |value_ptr| { - const user = value_ptr.*; - allocator.free(user.name); - allocator.destroy(user); - } - lookup.deinit(); -} -``` - -但在键不是值的一部分的情况下,我们需要迭代并释放这些键。在许多情况下,你需要同时迭代键和值并释放它们。我们可以通过释放键引用的名称而不是用户来模拟这一点: - -```zig -defer { - var it = lookup.iterator(); - while (it.next()) |kv| { - // 这个.. - allocator.free(kv.key_ptr.*); - - // 和下面的是一样的,但仅仅因为 user.name 是我们的键 - // allocator.free(user.name); - - allocator.destroy(kv.value_ptr.*); - } - lookup.deinit(); -} -``` - -我们使用 `iterator()` 而不是 `iteratorValue()` 来访问 `key_ptr` 和 `value_ptr`。 - -最后要考虑的是如何从我们的 `lookup` 中移除值。尽管使用了改进的清理逻辑,这段代码仍会导致键和堆分配的 `User` 泄漏: - -```zig -var lookup = std.StringHashMap(*User).init(allocator); - -defer { - var it = lookup.iterator(); - while (it.next()) |kv| { - allocator.free(kv.key_ptr.*); - allocator.destroy(kv.value_ptr.*); - } - lookup.deinit(); -} - -const user = try allocator.create(User); -user.* = .{ - .id = 9000, - .super = false, - // 模拟来自动态源(如数据库)的名称 - .name = try allocator.dupe(u8, "Goku"), -}; -try lookup.put(user.name, user); - -// 我们加上了这行! -_ = lookup.remove(user.name); -``` - -最后一行从我们的哈希表中移除了条目,所以我们的清理例程不再迭代它,也不会释放名称或用户。我们需要使用 `fetchRemove` 而不是 `remove` 来获取被移除的键和值: - -```zig -if (lookup.fetchRemove(user.name)) |kv| { - allocator.free(kv.key); - allocator.destroy(kv.value); -} -``` - -`fetchRemove` 不返回键和值的指针,而是返回实际的键和值。这并不会改变我们的使用方式,但显然为什么返回键和值而不是指针是很明显的,因为从哈希表中移除的条目,不再有指向哈希表中键和值的有效指针——它们已经被移除了。 - -所有这些都假设你的值和键在从哈希表中移除时需要被释放/失效。有些情况下,你的值(更少见的是键)的生命周期与它们在哈希表中的存在完全无关。在这些情况下,你需要在适合你的应用程序的情况下释放内存。没有通用的模式或指导适用。 - -对于大多数情况,在处理非原始键或值时,关键是当你调用哈希表的 `deinit` 时,你为键和值分配的任何内存不会被自动释放;你需要自己处理。 - -# getOrPut - -虽然我们已经讨论过的内容有很多含义,但对我来说,直接暴露键和值指针的最大好处之一是 `getOrPut` 方法。 - -如果我让你在 Go 或大多数语言中存储带名称的计数器,你会写出类似这样的代码: - -```go -count, exists := counters[name] -if exists == false { - counters[name] = 1 -} else { - counters[name] = count + 1; -} -``` - -这段代码需要两次查找。尽管我们被训练成不考虑哈希表访问通常为 O(1),实际情况是操作次数越少运行速度越快;而计算哈希码并非最经济的操作(其性能取决于键的类型和长度),碰撞还会增加额外开销。「getOrPut」方法通过返回一个值指针和一个指示是否找到该值的布尔值来解决这个问题。 - -换句话说,使用 `getOrPut` 我们要么获得一个指向找到的值的指针,要么获得一个指向应放置项位置的指针。我们还得到一个布尔值,用于指示是哪种情况。这使得上述插入或更新操作仅需一次查找: - -```zig -const gop = try counters.getOrPut(name); -if (gop.found_existing) { - gop.value_ptr.* += 1; -} else { - gop.value_ptr.* = 1; -} -``` - -当然,只要不对哈希表进行修改,`value_ptr` 就应被视为有效。顺便提一句,这同样适用于我们通过 `iterator()`、`valueIterator` 和 `keyIterator` 获取的迭代键和值,原因相同。 - -# 结论 - -希望你现在对使用「std.HashMap」、「std.AutoHashMap」和「std.StringHashMap」以及它们的「unmanaged」变体感到更加得心应手。虽然你可能永远不需要提供自己的上下文(例如「hash」和「eql」函数),但了解这是一个选项是有益的。在日常编程中,可视化数据尤其有用,尤其是在使用指针和添加间接层次时。每当我处理 `value_ptr` 或 `key_ptr` 时,我都会想到这些切片以及值或键与这些切片中值或键的实际地址之间的区别。 - -> 原文地址: https://www.openmymind.net/Zigs-HashMap-Part-2/ diff --git a/content/post/2024-06-16-leveraging-zig-allocator.md b/content/post/2024-06-16-leveraging-zig-allocator.md deleted file mode 100644 index 348a336..0000000 --- a/content/post/2024-06-16-leveraging-zig-allocator.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -title: "Zig 分配器的应用" -date: 2024-06-16T12:11:44+0800 ---- - -> 原文地址: - -假设我们想为Zig编写一个 [HTTP服务器库](https://github.com/karlseguin/http.zig)。这个库的核心可能是线程池,用于处理请求。以简化的方式来看,它可能类似于: - -```zig -fn run(worker: *Worker) void { - while (queue.pop()) |conn| { - const action = worker.route(conn.req.url); - action(conn.req, conn.res) catch { // TODO: 500 }; - worker.write(conn.res); - } -} -``` - -作为这个库的用户,您可能会编写一些动态内容的操作。如果假设在启动时为服务器提供分配器(Allocator),则可以将此分配器传递给动作: - -```zig -fn run(worker: *Worker) void { - const allocator = worker.server.allocator; - while (queue.pop()) |conn| { - const action = worker.route(conn.req.url); - action(allocator, conn.req, conn.res) catch { // TODO: 500 }; - worker.write(conn.res); - } -} -``` - -这允许用户编写如下的操作: - -```zig -fn greet(allocator: Allocator, req: *http.Request, res: *http.Response) !void { - const name = req.query("name") orelse "guest"; - res.status = 200; - res.body = try std.fmt.allocPrint(allocator, "Hello {s}", .{name}); -} -``` - -虽然这是一个正确的方向,但存在明显的问题:分配的问候语从未被释放。我们的`run`函数不能在写回应后就调用`allocator.free(conn.res.body)`,因为在某些情况下,主体可能不需要被释放。我们可以通过使动作必须 `write()` 回应并因此能够`free`它所做的任何分配来结构化API,但这将使得添加一些功能变得不可能,比如支持中间件。 - -最佳和最简单的方法是使用 `ArenaAllocator` 。其工作原理很简单:当我们`deinit`时,所有分配都被释放。 - -```zig -fn run(worker: *Worker) void { - const allocator = worker.server.allocator; - while (queue.pop()) |conn| { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - const action = worker.route(conn.req.url); - action(arena.allocator(), conn.req, conn.res) catch { // TODO: 500 }; - worker.write(conn.res); - } -} -``` - -`std.mem.Allocator` 是一个 "[接口](https://www.openmymind.net/Zig-Interfaces/)" ,我们的动作无需更改。 `ArenaAllocator` 对HTTP服务器来说是一个很好的选择,因为它们与请求绑定,具有明确/可理解的生命周期,并且相对短暂。虽然有可能滥用它们,但可以说:使用更多! - -我们可以更进一步并重用相同的Arena。这可能看起来不太有用,但是请看: - -```zig -fn run(worker: *Worker) void { - const allocator = worker.server.allocator; - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - while (queue.pop()) |conn| { - // 魔法在此处! - defer _ = arena.reset(.{.retain_with_limit = 8192}); - const action = worker.route(conn.req.url); - action(arena.allocator(), conn.req, conn.res) catch { // TODO: 500 }; - worker.write(conn.res); - } -} -``` - -我们将Arena移出了循环,但重要的部分在内部:每个请求后,我们重置了Arena并保留最多8K内存。这意味着对于许多请求,我们无需访问底层分配器(`worker.server.allocator`)。这种方法简化了内存管理。 - -现在想象一下,如果我们不能用 `retain_with_limit` 重置 Arena,我们还能进行同样的优化吗?可以,我们可以创建自己的分配器,首先尝试使用固定缓冲区分配器(FixedBufferAllocator),如果分配适配,回退到 Arena 分配器。 - -这里是 `FallbackAllocator` 的完整示例: - -```zig -const FallbackAllocator = struct { - primary: Allocator, - fallback: Allocator, - fba: *std.heap.FixedBufferAllocator, - - pub fn allocator(self: *FallbackAllocator) Allocator { - return .{ - .ptr = self, - .vtable = &.{.alloc = alloc, .resize = resize, .free = free}, - }; - } - - fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ra: usize) ?[*]u8 { - const self: *FallbackAllocator = @ptrCast(@alignCast(ctx)); - return self.primary.rawAlloc(len, ptr_align, ra) - orelse self.fallback.rawAlloc(len, ptr_align, ra); - } - - fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ra: usize) bool { - const self: *FallbackAllocator = @ptrCast(@alignCast(ctx)); - if (self.fba.ownsPtr(buf.ptr)) { - if (self.primary.rawResize(buf, buf_align, new_len, ra)) { - return true; - } - } - return self.fallback.rawResize(buf, buf_align, new_len, ra); - } - - fn free(_: *anyopaque, _: []u8, _: u8, _: usize) void { - // we noop this since, in our specific case, we know - // the fallback is an arena, which won't free individual items - } -}; -``` - -我们的`alloc`实现首先尝试使用我们定义的"主"分配器进行分配。如果失败,我们会使用"备用"分配器。作为`std.mem.Allocator`接口的一部分,我们需要实现的`resize`方法会确定正在尝试扩展内存的所有者,并然后调用其`rawResize`方法。为了保持代码简单,我在这里省略了`free`方法的具体实现——在这种特定情况下是可以接受的,因为我们计划使用"主"分配器作为`FixedBufferAllocator`,而"备用"分配器则会是`ArenaAllocator`(因此所有释放操作会在arena的`deinit`或`reset`时进行)。 - -接下来我们需要改变我们的`run`方法以利用这个新的分配器: - -```zig -fn run(worker: *Worker) void { - const allocator = worker.server.allocator; // 这是FixedBufferAllocator底层的内存 - const buf = try allocator.alloc(u8, 8192); // 分配8K字节的内存用于存储数据 - defer allocator.free(buf); // 完成后释放内存 - - var fba = std.heap.FixedBufferAllocator.init(buf); // 初始化FixedBufferAllocator - - while (queue.pop()) |conn| { - defer fba.reset(); // 重置FixedBufferAllocator,准备处理下一个请求 - - var arena = std.heap.ArenaAllocator.init(allocator); // 初始化ArenaAllocator用于分配额外内存 - defer arena.deinit(); - - var fallback = FallbackAllocator{ - .fba = &fba, - .primary = fba.allocator(), - .fallback = arena.allocator(), - }; // 创建FallbackAllocator,包含FixedBufferAllocator和ArenaAllocator - - const action = worker.route(conn.req.url); // 路由请求到对应的动作处理函数 - action(fallback.allocator(), conn.req, conn.res) catch { // 处理动作执行中的错误 }; - - worker.write(conn.res); // 写回响应信息给客户端 - } -} -``` - -这种方法实现了类似于在`retain_with_limit`中重置arena的功能。我们创建了一个可以重复使用的`FixedBufferAllocator`,用于处理每个请求的8K字节内存需求。由于一个动作可能需要更多的内存,我们仍然需要`ArenaAllocator`来提供额外的空间。通过将`FixedBufferAllocator`和`ArenaAllocator`包裹在我们的`FallbackAllocator`中,我们可以确保任何分配都首先尝试使用(非常快的)`FixedBufferAllocator`,当其空间用尽时,则会切换到`ArenaAllocator`。 - -我们通过暴露`std.mem.Allocator`接口,可以调整如何工作而不破坏`greet`。这不仅简化了资源管理(例如通过`ArenaAllocator`),而且通过重复使用分配来提高了性能(类似于我们做的`retain_with_limit`或`FixedBufferAllocator`的操作)。 - -这个示例应该能突出显示我认为明确的分配器提供的两个实际优势: - -1. 简化资源管理(通过类似`ArenaAllocator`的方式) -2. 通过重用分配来提高性能(例如我们之前在 `retain_with_limit` 或 `FixedBufferAllocator` 时所做的一样) diff --git a/content/post/2024-08-12-zoop.md b/content/post/2024-08-12-zoop.md deleted file mode 100644 index afc5adf..0000000 --- a/content/post/2024-08-12-zoop.md +++ /dev/null @@ -1,484 +0,0 @@ ---- -title: "zoop 实现原理分析" -author: 朱亚东 -date: 2024-08-12T14:31:22+08:00 ---- - -# zoop 是什么 - -zoop 是 zig 的一个 OOP 解决方案,详细信息可以看看 [zoop官网](https://zhuyadong.github.io/zoop-docs/)。 - -# 为什么不用别的 OOP 语言 - -简单的说,是我个人原因,必需使用 zig 的同时,还一定要用 OOP,所以有了 zoop。 - -# zoop 入门 - -## 类和方法 - -```zig -pub const Base = struct { - pub usingnamespace zoop.Fn(@This()); - mixin: zoop.Mixin(@This()), -} -``` - -2-3行是一个struct成为zoop类必需的两行,这样一来,`Base` 就成为了一个 zoop 的类。 - -创建 `Base` 的对象有两种方法: - -- 在堆上: `var obj = try Base.new(allocator);` -- 在栈上:`var obj = Base.make(); obj.initMixin();` - -栈上创建的对象必需调用对象的 `initMixin()` 方法,因为对象地址对 zoop 来说很重要,而在 `make()` 中是无法知道这个返回的对象会放在什么地址,只能通过外部调用 `initMixin()` 来通知 zoop 这个地址。 - -销毁对象的方法是 `obj.destroy()`,这是 zoop 自动为所有类加上的方法,不需要用户去定义。 - -我们可以给 `Base` 加上方法和字段,就是正常的 zig 方法和字段: - -```zig -pub const Base = struct { - pub usingnamespace zoop.Fn(@This()); - mixin: zoop.Mixin(@This()), - - name: []const u8, - - pub fn init(self: *Base, name: []const u8) void { - self.name = name; - } -} -``` - -因为创建 zoop 类对象的方法不是 `init()`,因此在 zoop 类中,一般把 `init()` 当作初始化方法而不是创建方法。这种常规的方法,是没法被子类继承的,属于类的私有方法。要定义可以继承的方法,需要用如下形式来定义: - -```zig -pub const Base = struct { - ...// 和上面一样,这里不写了 - - pub fn Fn(comptime T: type) type { - return zoop.Method(.{ - struct { - pub fn getName(this: *T ) []const u8 { - return this.cast(Base).name; - } - }, - }); - } -} -``` - -看起来有点怪,下面解释 zoop 实现原理的时候会解释这些奇怪的地方,现在我们先熟悉用法,不要在意这些细节^\_^。 - -上面的代码给 `Base` 添加了一个可以继承的方法 `getName()`。 - -## 类的继承 - -zoop 引入一个关键字 `extends` 用来实现继承,比如下面我们定义 `Base` 的子类 `Child`: - -```zig -pub const Child = struct { - // 表示我继承 Base - pub const extends = .{Base}; - - // 必要的两行 - pub usingnamespace zoop.Fn(@This()); - mixin: zoop.Mixin(@This()); - - // 我的初始化函数,里面调用父类的初始化函数 - pub fn init(self: *Child, name: []const u8) void { - self.cast(Base).init(name); - } -} -test { - const t = std.testing; - - var sub = try Child.new(t.allocator); - sub.init("sub"); - defer sub.destroy(); - const name = sub.getName(); // 使用继承来的方法 getName() - try t.expectEqualStrings(name, "sub"); -} -``` - -## 接口定义 - -zoop 中的接口,实际上是一个胖指针。下面我们定义一个接口 `IGetName`: - -```zig -pub const IGetName = struct { - // 定义接口的 `Vtable`,说明接口有哪些方法 - pub const Vtable = zoop.DevVtable(@This(), struct { - getName: *const fn(self: *anyopaque) []const u8, - }); - // 必需的一行 - pub usingnamespace zoop.Api(@This()); - - // 必需的两个字段 - ptr: *anyopaque, - vptr: *const Vtable, - - // 必需的胶水代码,用来调用 `Vtable` 里面的函数 - pub fn Api(comptime I:type) type { - return struct { - pub fn getName(self: I) []const u8 { - return self.vptr.getName(self.ptr); - } - } - } -} -``` - -上面的代码具体原理下面会说到,这里大家知道接口就是这样定义的就行了。上面的代码定义了接口 `IGetName`,这个接口有一个方法 `getName()`。 - -## 接口实现 - -上面的 `Base` 类正好也有个符合 `IGetName` 接口的方法 `getName()`,那我们修改一下 `Base` 的代码让它来实现 `IGetName` 接口: - -```zig -pub const Base = struct { - // 我实现 IGetName 接口 - pub const extends = .{IGetName}; - - // 以下省略 - ... -} -``` - -可以看到实现接口和继承用的同样一个关键字 `extends`。因为子类会继承父类的接口,所以这样一来,`Child` 也自动实现了 `IGetName` 接口。 - -## 方法重写和虚函数调用 - -我们修改上面 `Child` 的代码,重写 `getName()` 方法: - -```zig -pub const Child = struct { - ... - // 省略上面已知代码,下面代码重写了 Base.getName() - - pub fn Fn(comptime T: type) type { - return zoop.Method(.{ - struct { - pub fn getName(_: *T) []const u8 { - return "override"; - } - }, - }); - } -} -``` - -要注意,只有可继承方法才可以被重写,可继承方法和重写的方法都要通过上面 `pub fn Fn(comptime T: type) type` 这样的方式来定义。 -重写的方法,只有通过接口,才能进行虚函数调用,下面是例子: - -```zig -const t = std.testing; - -var child = try Child.new(t.allocator); -child.init("Child"); -defer child.destroy(); - -var base: *Base = child.cast(Base); - -// 不通过接口调用 getName() -try t.expectEqualStrings(child.getName(), "override"); -try t.expectEqualStrings(base.getName(), "Child"); - -// 通过接口调用(虚函数调用) getName(); -try t.expectEqualStrings(child.as(IGetName).?.getName(), "override"); -try t.expectEqualStrings(base.as(IGetName).?.getName(), "override"); -``` - -上面例子中 `cast`、`as` 属于 zoop 中的类型转换,详细可以参考 [zoop 类型转换](https://zhuyadong.github.io/zoop-docs/guide/as-cast) - -那么 zoop 的基本使用方法就介绍到这里,下面我们开始介绍 zoop 的实现原理。 - -# 预设场景 - -接下来的讨论基于如下的属于 `mymod` 模块的类和接口: - -```zig -/// 接口 IGetName -pub const IGetName = struct { - pub const Vtable = zoop.DevVtable(@This(), struct { - getName: *const fn(self: *anyopaque) []const u8, - }); - pub usingnamespace zoop.Api(@This()); - - ptr: *anyopaque, - vptr: *const Vtable, - - pub fn Api(comptime I:type) type { - return struct { - pub fn getName(self: I) []const u8 { - return self.vptr.getName(self.ptr); - } - } - } -} - -/// 接口 ISetName -pub const ISetName = struct { - pub const Vtable = zoop.DevVtable(@This(), struct { - setName: *const fn(self: *anyopaque, name: []const u8) void, - }); - pub usingnamespace zoop.Api(@This()); - - ptr: *anyopaque, - vptr: *const Vtable, - - pub fn Api(comptime I:type) type { - return struct { - pub fn setName(self: I, name: []const u8) void { - self.vptr.setName(self.ptr, name); - } - } - } -} - -/// 基类 Base -pub const Base = struct { - pub const extends = .{ISetName}; - pub usingnamespace zoop.Fn(@This()); - - name: []const u8, - mixin: zoop.Mixin(@This()), - - pub fn Fn(comptime T: type) type { - return zoop.Method(.{ - struct { - pub fn setName(this: *T, name: []const u8) void { - this.cast(Base).name = name; - } - }, - }); - } -} - -/// 子类 Child -pub const Child = struct { - pub const extends = .{Base, IGetName}; - pub usingnamespace zoop.Fn(@This()); - - mixin: zoop.Mixin(@This()), - - pub fn Fn(comptime T: type) type { - return zoop.Method(.{ - struct { - pub fn getName(this: *T) []const u8 { - return this.cast(Base).name; - } - }, - }); - } -} -``` - -接口有两个: - -- `IGetName`: 接口方法 `getName` -- `ISetName`: 接口方法 `setName` - -类有两个: - -- `Base`: 基类,实现接口 `ISetName` -- `Child`: 子类,继承 `Base`,并实现接口 `IGetName` - -# 核心数据结构 `zoop.Mixin(T)` - -我们看看两个类的 `mixin` 这个数据里面有什么: - -```zig -pub const VtableFunc = *const fn (ifacename: []const u8) ?*IObject.Vtable; -pub const SuperPtrFunc = *const fn (rootptr: *anyopaque, typename: []const u8) ?*anyopaque; - -zoop.Mixin(Base) = struct { - deallocator: ?std.mem.Allocator = null, - meta: struct { - rootptr: ?*anyopaque = null, - typeinfo: *struct { - typename: []const u8, - getVtable: VtableFunc, - getSuperPtr: SuperPtrFunc, - }, - }, - data: struct {}, -} - -zoop.Mixin(Child) = struct { - deallocator: ?std.mem.Allocator = null, - meta: struct { - rootptr: ?*anyopaque = null, - typeinfo: *struct { - typename: []const u8, - getVtable: VtableFunc, - getSuperPtr: SuperPtrFunc, - }, - }, - data: struct { - mymod_Base: Base, - }, -} -``` - -可以看出,两者的唯一的差别在于 `Child.mixin.data` 里面包含了一个 `Base`, 而 `Base.mixin.data` 里面是空的。说明在 zoop 中,类有多少个父类,则类的 `mixin.data` 中,就有多少个父类的数据。 - -我们再来看看 `mixin.meta` 这个数据。先看看 `rootptr` 这个字段,如果我们现在有一个 `Base` 对象 `base`,那么 `base.mixin.meta.rootptr == &base` 是成立的;如果现在有一个 `Child` 对象 `child`,那么如下两条成立: - -- `child.mixin.meta.rootptr == &child` -- `child.mixin.data.mymod_Base.mixin.meta.rootptr == &child` - -事实上,`child.mixin.data.mymod_Base.mixin.meta` 里面的内容就是完全复制的 `child.mixin.meta`,因为所有内层对象的 `mixin.meta` 都是复制的最外层那个对象的 `mixin.meta`,因而所有对象的 `rootptr` 都指向最外层对象,这也是为什么叫 `rootptr` 的原因。 - -再看看 `typeinfo` 字段,这个字段是一个有3个字段的结构: - -- `typename`: 这是 `rootptr` 指向对象的类型名 -- `getVtable`: 根据接口名获得接口 `Vtable` 的函数 -- `getSuperPtr`: 根据父类名获得 `mixin.data` 中父类指针 - -上面两个函数获取的都是最外层对象的数据。根据对 `mixin` 数据的分析,zoop 的类型转换的原理就很清楚了,大家可以参考官网上关于 [类型转换](https://zhuyadong.github.io/zoop-docs/guide/as-cast) 的内容。 - -# 动态构造类的方法、接口方法、和 `Vtable` - -OOP 概念中的继承,重写,虚函数,实质其实就是在编译时动态构造需要的方法和属性。zoop 中主要是通过通过 [zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple) 这个模块来进行编译时动态构造。 - -这部分需要大家有一定的 [zig comptime](https://ziglang.org/documentation/0.13.0/#comptime) 的知识,同时如果大家理解了这部分知识,那么 zoop 动态构造方法属性的部分实际不难理解。(建议同时也看看 [zig 圣经](https://course.ziglang.cc/advanced/comptime) 中和 `comptime` 有关的部分,写的很好) - -下面我介绍一下 zoop 中用到的 `comptime` 一些技巧,相信会对大家今后使用 zig 有帮助。 - -## `struct` 很万能 - -`comptime` 编程中,`struct` 是你最好的朋友,想在不同的 `comptime` 函数之间传递数据,最方便的方式,就是通过构造一个 `struct`,把想传递的数据通过 `pub const xxx = ...` 的方式传递出去,通过 `struct` 保存数据最好的地方,就在于这个数据在运行时也是可用的 (`struct` 中的常量,是保存在 exe 的 `.data` 区,运行时可见),[zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple) 就是通过这个方法实现的。 - -## 动态构造 `struct` 的字段,用 `@Type()` - -网上好像很少有关于 `@Type()` 的使用说明,一般都是通过看 `zig.std` 的代码来学习,那我这里就稍微说明一下,希望能对大家有帮助。 -目前 zig 通过 `@Type()`,能动态构造的 `struct`,只有纯字段类型的 `struct` (个人理解)。构造的方法,就是先把计算好的一个 `std.builtin.Type.StructField` 数组传递给 `@Type()` 来返回一个 `struct`,比如以下代码: - -```zig -fn GenStruct() type { - comptime var fields:[2]std.builtin.Type.StructField = undefined; - fields[0] = .{ - .name = "age", - .type = i32, - .default_value = null, - .is_comptime = false, - .alignment = @alignOf(i32), - }; - fields[1] = .{ - .name = "name", - .type = []const u8, - .default_value = null, - .is_comptime = false, - .alignment = @alignOf([]const u8), - }; - - return @Type(.{ - .Struct = .{ - .layout = .auto, - .fields = fields[0..], - .decls = &.{}, - .is_tuple = false, - } - }); -} - -const MyStruct = GenStruct(); -``` - -这样上面的 `MyStruct` 就相当于: - -```zig -const MyStruct = struct { - age: i32, - name: []const u8, -}; -``` - -zoop 动态构造 `Vtable` 就是通过这个方法做到的,参考 [zoop.DefVtable 原理](https://zhuyadong.github.io/zoop-docs/reference/principle#DefVtable) 和 [zoop 源代码](https://github.com/zhuyadong/zoop.git) - -## 动态构造 `struct` 的函数,用 `usingnamespace` - -要想定义 `struct` 中的函数,理论上代码是一定要写在 `struct` 中的,目前 zig 唯一留下的一个口子,就是 `usingnamespace`,zoop 正是利用这个特性,来动态构造 `struct` 的函数。 - -我们回顾一下 `Base` 中定义 `setName` 方法的代码: - -```zig -pub fn Fn(comptime T: type) type { - return zoop.Method(.{ - struct { - pub fn setName(this: *T, name: []const u8) void { - this.cast(Base).name = name; - } - }, - }); -} -``` - -这里 `zoop.Method()` 返回的是什么呢,返回的是: - -```zig -struct { - pub const value = .{ - struct { - pub fn setName(this: *T, name: []const u8) void { - this.cast(Base).name = name; - } - }, - }; -} -``` - -通过返回一个 `struct` 的方式,在它的 `value` 常量中保存了一个 `tuple`,`tuple` 有一个带有方法 `setName` 的 `struct` 元素。众所周知,`tuple` 是可以各种组合的 (参考 [zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple)),于是 zoop 通过 [zoop.Fn](https://zhuyadong.github.io/zoop-docs/reference/zoop#Fn),比如上例中 `Child` 中的 `pub usingnamespace zoop.Fn(@This())`,把 `Child` 类型代入 `Base.Fn` 中,就相当于在 `Child` 内写了如下代码: - -```zig -pub usingnamespace struct { - pub fn setName(this: *Child, name: []const u8) void { - this.cast(Base).name = name; - } - }; -``` - -因而实现了对 `Base.setName()` 方法的继承。 - -## 运行时根据类型找 `Vtable` 和父类指针 - -这个功能的实现当时第一版是使用的 `std.StaticStringMap` 保存了一个类中所有接口名到接口 `Vtable` ,以及父类名到父类数据在本类中的地址偏移的映射。和 C++ 的 `dynamic_cast` 比起来,性能是比较差的。后来看到西瓜大大发的一个链接 [点这里](https://github.com/SuperAuguste/cursed-zig-errors),忽然意识到这不就是我一直想要的 `comptime` 全局变量么,我终于能写出 `typeId(comptime T: type) u32` 这样的函数了: - -```zig -fn typeId(comptime T: type) u32 { - return @intCast(@intFromError(@field(anyerror, "#" ++ @typeName(T)))); -} -``` - -这里利用的,就是上面说到的链接中提示到的一个 zig 的 `error` 类型的特点:访问 `error` 中不存在的值,zig 会生成一个唯一的新值返回,并且这个在 `comptime` 的时候同样有效。 - -有了 `typeId()` 函数,上面的事从根据类型名查哈希表,就变成了在数组中找同样的 `typeid` 了,整数比较对比字符串比较,那性能是快了好几倍的,根据我的了解,C++ 的 `dynamic_cast` 也是在数组中比较 `typeid`,这样一来,zoop 的动态转换性能,就和 C++ 差不多了。 - -利用这个新的 `typeId()` 函数,zoop 怎么做动态类型转换,我从 zoop 中抄一段代码大家一看就明白: - -```zig -/// 返回一个函数,函数的功能是输入接口 `typeid`,返回针对T的该接口的 Vtable -fn getVtableFunc(comptime T: type) VtableFunc { - // 先找出 T 实现的所有接口 - const ifaces = tuple.Append(.{IObject}, Interfaces(T)).value; - - // 所有接口的 typeid 和 Vtable 编译时计算好并保存在 kvs 数组中 - const KV = struct { typeid: u32, vtable: *IObject.Vtable }; - comptime var kvs: [ifaces.len]KV = undefined; - inline for (ifaces, 0..) |iface, i| { - kvs[i] = .{ .typeid = typeId(iface), .vtable = @ptrCast(makeVtable(T, RealVtable(iface))) }; - } - - return (struct { - pub fn func(typeid: u32) ?*IObject.Vtable { - // 根据 typeid,从 kvs 数组中找出 Vtable - for (kvs) |kv| { - if (kv.typeid == typeid) return kv.vtable; - } - return null; - } - }).func; -} -``` - -上面是找 `Vtable` 的实现,找父类指针的实现原理是一样的,大家可以去看 zoop 源代码了解细节。 diff --git a/content/post/2024-11-26-typed-fsm.md b/content/post/2024-11-26-typed-fsm.md deleted file mode 100644 index 69bbb22..0000000 --- a/content/post/2024-11-26-typed-fsm.md +++ /dev/null @@ -1,554 +0,0 @@ ---- -title: "在 zig 中实现类型安全的有限状态机" -author: sdzx -date: 2024-11-26T13:32:00+08:00 ---- - -# 1. 简单介绍类型化有限状态机的优势 - -## 1.1 介绍有限状态机 - -有限状态机(FSM,以下简称状态机)是程序中很常见的设计模式。 - -它包含两个主要的概念状态和消息。状态机程序整体上的行为就是不断地产生消息,处理消息。 - -而状态主要是在代码层面帮助人们理解消息的产生和处理。 - -## 1.2 [typed-fsm-zig](https://github.com/sdzx-1/typed-fsm-zig) - -typed-fsm-zig 是一个利用 zig 类型系统加一些编程规范实现的一个库,用于实现类型安全的有限状态机。 - -它具有以下两点优势: - -1. 类型安全,极大方便代码的编写,修改和重构 - 手写状态机在实际代码中有很大的心智负担,对于它的修改和重构更是如噩梦一样。 - - typed-fsm-zig 在类型上跟踪状态机的变化,使消息的定义,产生,处理都和状态相关联,从而让类型系统帮我们检查这个过程中是否存在状态错误。 - - 在编写,修改和重构的时候,任何状态的错误都会产生编译错误,而这些编译错误能帮助我们快速找到问题,解决问题。 - - > PS:推荐在 zls 中打开保存时检查,这样你几乎能得到一个交互式的状态机开发环境。 - -2. 简单高效,无任何代码生成,能方便与现有逻辑整合 - - typed-fsm-zig 是一种编程的思想,掌握这种思想就能方便的使用它。 - - 在实际的使用中没有任何的代码生成,除了一处隐式的约束要求之外,没有任何其它的控制,开发者完全掌握状态机,因此你可以方便的将它和你现有的代码结合起来。 - -# 2. 例子:修改 ATM 状态机的状态 - -这里我将以一个 ATM 状态机(以下简称 ATM)的例子来展示 typed-fsm-zig 和 zig 的类型系统如何帮助我快速修改 ATM 的状态。 - -为了简单性,这里我不展示构建 ATM 这个例子的过程,感兴趣的可以在这里看到[代码](https://github.com/sdzx-1/typed-fsm-zig/blob/master/examples/atm-gui.zig)。 - -## 2.1 介绍 ATM 状态机 - -ATM 代表自动取款机,因此它的代码的逻辑就是模拟自动取款机的一些行为:插入银行卡,输入 pin,检查 pin,取钱,修改 pin。 - -它的状态机整体如下: - -![ATM](/images/typed-fsm/2.1-1.webp) - -图中椭圆形表示状态,箭头表示消息。 -它包含五种状态:exit, ready, cardInserted, session, changePin。 - -同时它也包含一堆的消息,每个消息都包含了系统状态的转化。 -比如消息 InsertCard 代表将 ATM 的状态从 ready 转化到 cardInserted,这代表用户插入卡。 - -消息 Incorrect 代表将 ATM 的状态从 cardInserted 转化到 cardInserted, -这代表了一种循环,表示用户输错了 pin,但是可以再次尝试输入 pin,当然我们要求最多可以尝试三次。 - -整个程序效果如下: - -![ATM](/images/typed-fsm/2.1-2.webp) - -这里注意消息 Update,它代表更新 pin,同时将状态转从 changePin 换到 ready。 - -![ATM](/images/typed-fsm/2.1-4.webp) - -实际的表现就是在 changePin 的界面中我们修改 pin,然后点击 Change 按钮触发 Update 消息,修改 pin,并返回到 ready 界面。 - -![ATM](/images/typed-fsm/2.1-3.webp) - -接下来的文章中我将修改 Update 的行为,并展示在这个过程中类型系统如何帮助我快速调整代码。 - -## 2.2 修改 Update 消息 - -实际的消息 Update 定义代码如下 - -```zig - pub fn changePinMsg(end: AtmSt) type { - return union(enum) { - Update: struct { v: [4]u8, wit: WitFn(end, .ready) = .{} }, - ... - } - } - -``` - -这里的.ready 就代表了处理完 Update 消息后就会进入 ready 状态。 - -我们修改这里,把它变成.cardInserted,这代表了我们要求更新完 pin 之后进入 cardInserted 界面重新输入新的 pin,这看着是个合理的要求。 - -新的状态图如下: - -![ATM](/images/typed-fsm/2.2-1.webp) - -这时如果我重新编译代码,那么类型系统就会产生下面的错误: - -```shell - -➜ typed-fsm-zig git:(doc) ✗ zig build atm-gui -atm-gui -└─ run atm-gui - └─ zig build-exe atm-gui Debug native 1 errors -examples/atm-gui.zig:301:60: error: expected type 'typed-fsm.Witness(atm-gui.AtmSt,.exit,.ready)', found 'typed-fsm.Witness(atm-gui.AtmSt,.exit,.cardInserted)' - @call(.always_tail, readyHandler, .{ val.wit, ist }); - ~~~^~~~ -src/typed-fsm.zig:9:20: note: struct declared here (2 times) - return struct { - ^~~~~~ -examples/atm-gui.zig:254:46: note: parameter type declared here -pub fn readyHandler(comptime w: AtmSt.EWitness(.ready), ist: *InternalState) void { - ~~~~~~~~~~~~~~^~~~~~~~ -referenced by: - cardInsertedHander__anon_6916: examples/atm-gui.zig:271:13 - readyHander__anon_3925: examples/atm-gui.zig:261:13 - 5 reference(s) hidden; use '-freference-trace=7' to see all references - -``` - -它告诉我们在 301 行存在类型不匹配。因为之前的状态是 ready 所以使用 readyHandler。 - -当我们把 Update 的状态修改为 cardInserted 时,它与 readyHandler 类型不匹配,应该将它修改为 cardInsertedHandler。 - -修改之后的代码如下: - -```zig -@call(.always_tail, cardInsertedHandler, .{ val.wit, ist }); -``` - -在这里类型系统精确的告诉了我们需要修改的地方,以及原因。修改完成后程序即能正确运行。 - -## 2.3 移除 changePin 状态 - -这一节中我们尝试移除 changePin 状态,看看类型系统会给我们什么反馈。 -如果移除 changePin,新的状态图如下: - -![ATM](/images/typed-fsm/2.3-1.webp) - -重新编译项目,将获得类型系统的反馈 - -类型系统的反馈首先是: - -```shell -examples/atm-gui.zig:148:36: error: enum 'atm-gui.AtmSt' has no member named 'changePin' - ChangePin: WitFn(end, .changePin), - ~^~~~~~~~~ -``` - -因为 changePin 状态已经被移除,因此消息 ChangePin(它代表了从 session 进入 changePin 状态)也不应该再存在了,我们移除它再重新编译。 - -新的反馈如下: - -```shell -examples/atm-gui.zig:161:64: error: union 'atm-gui.AtmSt.sessionMsg(.exit)' has no member named 'ChangePin' - if (resource.changePin.toButton()) return .ChangePin; - ~^~~~~~~~~ -``` - -我们移除 ChangePin 消息,因此也将它从消息产生的地方移除,继续重新编译。 - -新的反馈如下: - -```shell -examples/atm-gui.zig:296:10: error: no field named 'ChangePin' in enum '@typeInfo(atm-gui.AtmSt.sessionMsg(.exit)).@"union".tag_type.?' - .ChangePin => |wit| { - ~^~~~~~~~~ -``` - -因为消息 ChangePin 已经不在了,也应将它从消息处理的地方移除,继续重新编译。 - -这一次不再有编译错误产生,我们搞定了一个新的程序,它不再包含 changePin 的逻辑。 - -在这个过程中类型系统帮助我们找到问题和原因。这非常酷!!! - -## 2.4 总结 - -以上是一个简单的例子,展示了 typed-fsm-zig 对于提升状态机编程体验的巨大效果。 - -展示类型系统如何帮助我们指示错误的地方,把复杂的状态机修改变成一种愉快的编程经历。 - -还有些没有讲到的优势如下: - -1. 状态的分离,后端 handler 处理业务的状态变化,前端渲染和消息生成不改变状态。 -2. 消息生成受到类型的限制和状态相关,这样避免错误消息的产生。 - -这些优势对于复杂业务有很大的帮助。 - -接下来我将介绍 typed-fsm-zig 的原理和实现。 - - - -# 3. 原理与实现 - -最开始的版本是[typed-fsm](https://github.com/sdzx-1/typed-fsm),由使用 haskell 实现,它实现了完整类型安全的有限状态机。 - -typed-fsm 基于[Mcbride Indexed Monad](https://hackage.haskell.org/package/typed-fsm-0.3.0.1/docs/Data-IFunctor.html): - -```haskell -type a ~> b = forall i. a i -> b i - -class IMonad m where - ireturn :: a ~> m a - ibind :: (a ~> m b) -> (m a ~> m b) -``` - -这是一种特殊的 monad,能在类型上为不确定状态建模。 - -而在 zig 实现中移除了对 Monad 语义的需求,保留了在类型上追踪状态的能力。 - -所以它不具备完整的类型安全的能力,需要依靠编程规范来约束代码的行为。我认为这样的取舍是值得的,它的类型安全性在 zig 中完全够用。 - -以下是一个原型例子,它包含了 typed-fsm-zig 的核心想法。看不懂不需要担心,接下来我将详细解释这些代码。 - -```zig -const std = @import("std"); - -pub fn main() !void { - var val: i32 = 0; - const s1Wit = Witness(Exmaple, .exit, .s1){}; - _ = s1Handler(s1Wit, &val); -} - -pub fn Witness(T: type, end: T, start: T) type { - return struct { - pub fn getMsg(self: @This()) @TypeOf(start.STM(end).getMsg) { - _ = self; - if (end == start) @compileError("Can't getMsg!"); - return start.STM(end).getMsg; - } - - pub fn terminal(_: @This()) void { - if (end != start) @compileError("Can't terminal!"); - return {}; - } - }; -} -const Exmaple = enum { - exit, - s1, - s2, - - // State to Message union - pub fn STM(start: Exmaple, end: Exmaple) type { - return switch (start) { - .exit => exitMsg(end), - .s1 => s1Msg(end), - .s2 => s2Msg(end), - }; - } -}; - -pub fn exitMsg(_: Exmaple) void { - return {}; -} - -pub fn s1Msg(end: Exmaple) type { - return union(enum) { - Exit: Witness(Exmaple, end, .exit), - S1ToS2: Witness(Exmaple, end, .s2), - pub fn getMsg(ref: *const i32) @This() { - if (ref.* > 20) return .Exit; - return .S1ToS2; - } - }; -} -pub fn s2Msg(end: Exmaple) type { - return union(enum) { - S2ToS1: Witness(Exmaple, end, .s1), - pub fn getMsg() @This() { - return .S2ToS1; - } - }; -} - -fn s1Handler(val: Witness(Exmaple, .exit, .s1), ref: *i32) void { - std.debug.print("val: {d}\n", .{ref.*}); - switch (val.getMsg()(ref)) { - .Exit => |wit| wit.terminal(), - .S1ToS2 => |wit| { - ref.* += 1; - s2Handler(wit, ref); - }, - } -} -fn s2Handler(val: Witness(Exmaple, .exit, .s2), ref: *i32) void { - switch (val.getMsg()()) { - .S2ToS1 => |wit| { - ref.* += 2; - s1Handler(wit, ref); - }, - } -} - -``` - -首先是 Witness,它是一个类型上的证据,用来跟踪类型上状态的变化。 - -这里有一些介绍 Witness 思想的[文章 1](https://wiki.haskell.org/Type_witness),[文章 2](https://serokell.io/blog/haskell-type-level-witness)。 - -感兴趣的可以看一下,看懂这些要求你了解 GADT,上面提到的 Mcbirde Indexed Monad 本质就是在 GADT 类型上的 monad。 - -在这里的 Winess 三个参数分别表示: - -1. T 表示状态机的类型, -2. end 表示终止时的状态, -3. start 表示当前的状态。 - -它还有两个函数: - -1. getMsg 表示从外部获取消息的函数 -2. terminal 表示终止状态机的函数。 - -当 end == start 时表示当前处于终止状态,因此 Witness 只能使用 terminal 函数,当 end != start 时表示当前不处于终止状态,应该继续从外部获取消息,因此 Witness 只能使用 getMsg 函数。 - -```zig -pub fn Witness(T: type, end: T, start: T) type { - return struct { - pub fn getMsg(self: @This()) @TypeOf(start.STM(end).getMsg) { - if (end == start) @compileError("Can't getMsg!"); - _ = self; - return start.STM(end).getMsg; - } - - pub fn terminal(_: @This()) void { - if (end != start) @compileError("Can't terminal!"); - return {}; - } - }; -} - -``` - -我们在这里定义状态。Example 包含三个状态:exit,s1,s2。我们将在类型上跟踪这些状态的变化。 - -注意这里的 STM 函数,它代表如何将状态映射到对应的消息集合。在实际 typed-fsm-zig 的代码中,这就是我所说的那一处隐式的约束要求。 - -实际代码中会将消息集合整合在 enum 的内部,使用特殊的命名规范将状态与消息集合对应。目前的隐式规范是在状态后面加上 Msg。 - -```zig -const Exmaple = enum { - exit, - s1, - s2, - - // State to Message union - pub fn STM(start: Exmaple, end: Exmaple) type { - return switch (start) { - .exit => exitMsg(end), - .s1 => s1Msg(end), - .s2 => s2Msg(end), - }; - } -}; - -``` - - - -接下来是消息的定义和产生, - -```zig -// exit 状态下没有任何消息 -pub fn exitMsg(_: Exmaple) void { - return {}; -} - -// s1 状态下有两个消息 Exit 和 S1ToS2, 他们分别将状态转化为 exit 和 s2 -pub fn s1Msg(end: Exmaple) type { - return union(enum) { - Exit: Witness(Exmaple, end, .exit), - S1ToS2: Witness(Exmaple, end, .s2), - - // getMsg 函数表明在 s1 状态下如何产生消息,这里受到类型系统的约束, - // 在 s1 的状态下不会产生其它状态的消息 - pub fn getMsg(ref: *const i32) @This() { - if (ref.* > 20) return .Exit; - return .S1ToS2; - } - }; -} - -// s2 状态下有一个消息 S2ToS1 -pub fn s2Msg(end: Exmaple) type { - return union(enum) { - S2ToS1: Witness(Exmaple, end, .s1), - - pub fn getMsg() @This() { - return .S2ToS1; - } - }; -} - -``` - - - -最后一部分是消息的处理。 - -整体的逻辑是通过 Witness 的 getMsg 函数从外部获取消息,然后通过模式匹配处理消息。 -每个消息又包含接下来状态的 Witness,然后使用对应的函数处理这些 Witness。 - -通过 Witness 让类型系统帮我们检查函数的调用是否正确。 - -通过对消息进行模式匹配,编译器能确定我们是否正确且完整的处理了所有的消息。 - -这些对于代码的编写,修改,重构都有巨大的帮助。 - -```zig -fn s1Handler(val: Witness(Exmaple, .exit, .s1), ref: *i32) void { - std.debug.print("val: {d}\n", .{ref.*}); - switch (val.getMsg()(ref)) { - .Exit => |wit| wit.terminal(), - .S1Tos2 => |wit| { - ref.* += 1; - s2Handler(wit, ref); - }, - } -} -fn s2Handler(val: Witness(Exmaple, .exit, .s2), ref: *i32) void { - switch (val.getMsg()()) { - .S2Tos1 => |wit| { - ref.* += 2; - s1Handler(wit, ref); - }, - } -} - -``` - -以上就是 typed-fsm-zig 核心想法的完整介绍。接下来我将介绍需要的编程规范。 - -# 4. typed-fsm-zig 需要哪些编程规范 - -1. 状态和消息集合之间需要满足的隐式命名规范 - - 以 ATM 为例: - - exit -- exitMsg - - ready -- readyMsg - - cardInserted -- cardInsertedMsg - - session -- sessionMsg - -```zig - -const AtmSt = enum { - exit, - ready, - cardInserted, - session, - - pub fn exitMsg(_: AtmSt) type { - return void; - } - - pub fn readyMsg(end: AtmSt) type { - return union(enum) { - ExitAtm: WitFn(end, .exit), - InsertCard: WitFn(end, .cardInserted), - - pub fn genMsg() @This() { - ... - } - }; - } - - pub fn cardInsertedMsg(end: AtmSt) type { - return union(enum) { - Correct: WitFn(end, .session), - Incorrect: WitFn(end, .cardInserted), - EjectCard: WitFn(end, .ready), - - pub fn genMsg(ist: *const InternalState) @This() { - ... - } - }; - } - - pub fn sessionMsg(end: AtmSt) type { - return union(enum) { - Disponse: struct { v: usize, wit: WitFn(end, .session) = .{} }, - EjectCard: WitFn(end, .ready), - - pub fn genMsg(ist: *const InternalState) @This() { - ... - } - }; - } -}; -``` - -2. 除了 exit 状态外,其它消息需要包含 genMsg 函数用于产生消息,任何消息都必须带有 Witness - -3. 状态机都需要定义退出状态,尽管你可能永远也不会退出状态机,但退出状态作用于类型上,是不可缺少的 - -4. 在互相调用其它 handler 的时候使用尾递归的语法,并且在必须在语句块最后处理消息附带的 Witness - -由于 zig 的实现缺少 Mcbride Indexed Monad 语义的支持,因此类型系统不能阻止你进行下面的操作: - -```zig - -// 使用上面 Example 的处理函数 s1Handler, 将它修改成下面的样子。 -// 这里的 s1Handler 不应该被多次调用,在 haskell 版本的 typed-fsm 中,类型系统能检查出这里类型错误,但是在 zig 实现中无法做到, -// 因此我们要求只能在语句块最后有一个处理 Witness 的语句 -fn s2Handler(val: Witness(Exmaple, .exit, .s2), ref: *i32) void { - switch (val.getMsg()()) { - .S2Tos1 => |wit| { - ref.* += 2; - s1Handler(wit, ref); - s1Handler(wit, ref); - s1Handler(wit, ref); - s1Handler(wit, ref); - }, - } -} - -``` - -由于状态机需要长期运行,在互调递归的函数中如果不使用尾递归会导致栈溢出。 - -因此上面的 Example demo 中,如果我将 20 改成很大的值,比如二百万,那么一定会发生栈溢出,因为 demo 中的调用没采用尾递归的方式。 - -在实际的 ATM 例子中他们的调用方式是: - -```zig -pub fn readyHandler(comptime w: AtmSt.EWitness(.ready), ist: *InternalState) void { - switch (w.getMsg()()) { - .ExitAtm => |witness| { - witness.terminal(); - }, - .InsertCard => |witness| { - ist.times = 0; - @call(.always_tail, cardInsertedHandler, .{ witness, ist }); - }, - } -} - -``` - -这里的 `@call(.always_tail, cardInsertedHandler, .{ witness, ist })` 就是 zig 中尾递归语法(参见:[ziglang #694](https://github.com/ziglang/zig/issues/694#issuecomment-563310662))。出于这个语法的需要,处理函数中的 Witness 被变成了编译时已知(这里是 `comptime w: AtmSt.EWitness(.ready)`)。 - -遵循这四点要求,就能获得强大的类型安全保证,足以让你愉快的使用状态机! - -# 5. 接下来能够增强的功能 - -暂时我能想到的有如下几点: - -1. 在状态机中,消息的产生和处理分开,因此可以定义多个消息产生的前端,处理部分可以任意切换消息产生的前端。比如我们可以定义测试状态机前端,用于产生测试数据,当处理部分调用测试前端的代码时就能测试整个状态机的行为。 -2. 支持子状态,这会让类型更加复杂。 -3. 开发基于[typed-fsm-zig 的 gui 系统](https://discourse.haskell.org/t/try-to-combine-typed-fsm-with-gui-to-produce-unexpected-abstract-combinations/10026),状态机在 gui 有很高的实用性,将他们结合是一个不错的选择。 -4. 开发 typed-session-zig,实现类型安全的通信协议。我在 haskell 已经实现了一个[实用的类型安全的多角色通讯协议框架](https://github.com/sdzx-1/typed-session),应该可以移植到 zig 中。 diff --git a/content/post/2025-01-23-bonkers-comptime.md b/content/post/2025-01-23-bonkers-comptime.md deleted file mode 100644 index bf6e02f..0000000 --- a/content/post/2025-01-23-bonkers-comptime.md +++ /dev/null @@ -1,389 +0,0 @@ ---- -title: Zig comptime 棒极了 -author: xihale -date: 2025-01-23T12:00:00+08:00 ---- - -> 原文: - -> 译注:原文中的代码块是交互式,翻译时并没有移植。另外,由于 comptime 本身即是关键概念,并且下文的意思更侧重于 Zig comptime 的特性,故下文大多使用 comptime 代替编译时概念。 - -## 引子 - -编程通过自动化地处理数据极大地提升了生产力。而元编程则让我们可以像处理数据一样处理代码,以此将编程的力量反向作用于编程自身。而在底层编程中,我想元编程可能带来最大的优势,因为那些高级概念必须得精确映射到某些低级操作。然而,除了函数式编程语言外,我一直觉得各编程语言对元编程的实现并不理想。因此,当我看到 Zig 把元编程列为一个主要特性时,我提起了很大的兴趣。 - -说实话,刚开始使用 Zig 的 comptime 时,我的体验相当糟糕。那些概念对我而言很陌生,而想要实现预期的效果也很困难。不过后来,当我转换了思路,一切都迎刃而解了,由此,我突然就喜欢上了它。现在,为了帮助你更快地走上这条探索之路,下面我将介绍六种不同的“视角”来理解 comptime。每个视角都从不同的角度,帮助你将已有的编程知识应用到 Zig 中。 - -这并不是一本完整涵盖了 comptime 的所有所需知识的详细指南。相反,它更侧重于提供多种策略,从不同视角帮助你全面地理解该如何以 comptime 的角度思考问题。 - -为了明确起见,所有示例都是有效的 Zig 代码,但示例中的转换只是概念性的,它们并不是 Zig 实际的实现方式。 - -## 视角0: 忽略它 - -我说我喜欢这个特性,却又立刻叫你忽略它,这确实有点怪。但我认为此处正是 Zig comptime 威力所体现的地方,所以我将从这里出发。Zig Zen 中的第三条是“倾向于阅读代码,而不是编写代码。”确实,能够轻松地阅读代码在各种情况下都很重要,因为它是建立概念理解的基础,而这种理解也是调试或修改代码所必需的。 - -元编程很容易让人陷入“只写代码”的境地。如果你在使用基于宏的元编程或代码生成器,那么代码就会变成两种版本:源代码和展开后的代码。这个额外的间接层使得从阅读到调试代码的整个过程都变得更加困难。当你要改变程序的行为时,你不仅需要确定生成的代码应该是什么样的,还需要弄清楚该如何通过元编程来生成这些代码。 - -但在 Zig 中,这些额外的开销是完全不需要的。你可以简单地忽略代码在不同时间执行这一隐形的前提条件,而在概念上直接将运行时和编译时的区别忽略掉再来理解那些代码。为了演示这一点,让我们一步一步来看两个不同的代码示例。第一个是普通的运行时代码,第二个则是利用了 comptime 的代码。 - -> 普通的运行时代码 - -```Zig -pub fn main() void { - const array: [3]i64 = .{1,2,3}; - var sum: i64 = 0; - for (array) |value| { - sum += value; - } - std.debug.print("array's sum is {d}.\n", .{sum}); -} -``` - -点击“下一步”逐步执行程序,观察状态的变化。这个例子很简单:对一组数字求和。现在我们来做些奇怪的事:对一个结构体的字段求和。虽然这个例子有些牵强,但却能够很好地展示这一概念。 - -> 基于 comptime 的代码 - -```Zig -const MyStruct = struct { - a: i64, - b: i64, - c: i64, -}; - -pub fn main() void { - const my_struct: MyStruct = .{ - .a = 1, - .b = 2, - .c = 3, - }; - - var sum: i64 = 0; - inline for (comptime std.meta.fieldNames(MyStruct)) |field_name| { - sum += @field(my_struct, field_name); - } - std.debug.print("struct's sum is {d}.\n", .{sum}); -} -``` - -与数组求和的例子相比,这个 comptime 示例引入的新东西几乎是微不足道的。这正是 comptime 的重点!这段代码的可执行文件效率和你在 C 中为结构体类型手写一个求和函数一样高效,而它却看起来像是你在使用支持运行时反射的语言编写的。虽然这不是 Zig 实际的工作方式,但这也不完全是一个纯粹的理论练习:Zig 核心团队正在开发一个调试器,允许你像这个例子一样逐步执行混合了编译时和运行时的代码。 - -Zig 中有很多基于 comptime 且远远不止这样简单的类型反射,但你只需要阅读那些代码、完全无需深入了解其中有关 comptime 的细节就可以理解它们在干什么。当然,如果你想使用 comptime 编写代码,则不能仅仅止步于此,让我们继续深入。 - -## 视角1: 泛型 - -泛型在 Zig 中并不是一个特定的功能。相反,Zig 中的仅仅一小部分的 comptime 特性就可以提供用来处理你进行泛型编程所需的一切。这种视角虽然不能让你完全理解 comptime,但它确实为你提供了一个入口点,借此,你可以完成基于元编程的许多任务。 - -要使一个类型成为泛型,只需将其定义包裹在一个接受类型并返回类型的函数中。(译注:由于 Zig 中类型是一等公民,所以面向类型的编程是合法且常见的) - -```Zig -pub fn GenericMyStruct(comptime T: type) type { - return struct { - a: T, - b: T, - c: T, - - fn sumFields(my_struct: GenericMyStruct(T)) T { - var sum: T = 0; - const fields = comptime std.meta.fieldNames(GenericMyStruct(T)); - inline for (fields) |field_name| { - sum += @field(my_struct, field_name); - } - return sum; - } - }; -} - -pub fn main() void { - const my_struct: GenericMyStruct(i64) = .{ - .a = 1, - .b = 2, - .c = 3, - }; - std.debug.print("struct's sum is {d}.\n", .{my_struct.sumFields()}); -} -``` - -泛型函数也可以如此实现。 - -```Zig -fn quadratic(comptime T: type, a: T, b: T, c: T, x: T) T { - return a * x*x + b * x + c; -} - -pub fn main() void { - const a = quadratic(f32, 21.6, 3.2, -3, 0.5); - const b = quadratic(i64, 1, -3, 4, 2); - std.debug.print("Answer: {d}{d}\n", .{a, b}); -} -``` - -当然,也可以通过使用特殊类型 anytype 来推断参数的类型,而这通常在参数的类型对函数签名的其余部分没有影响时使用。(译注:此时要限制 a, b, c 的类型相同,所以此处不用 anytype ) - -## 视角2:编译时运行的标准代码 - -这是一个古老的故事: 增加一种自动执行命令的方法。当然,你还需要变量。 哦,还有条件。 拜托,能给我循环吗?这些看似合理的需求,最终导致这些自动化命令变得越来越复杂,甚至演变成一个完整的宏语言。 但 Zig 不同, 在运行时、编译时,甚至是构建系统中都使用了相同的语言。 - -考虑经典的 Fizz Buzz。 - -```Zig -fn fizzBuzz(writer: std.io.AnyWriter) !void { - var i: usize = 1; - while (i <= 100) : (i += 1) { - if (i % 3 == 0 and i % 5 == 0) { - try writer.print("fizzbuzz\n", .{}); - } else if (i % 3 == 0) { - try writer.print("fizz\n", .{}); - } else if (i % 5 == 0) { - try writer.print("buzz\n", .{}); - } else { - try writer.print("{d}\n", .{i}); - } - } -} - -pub fn main() !void { - const out_writer = std.io.getStdOut().writer().any(); - try fizzBuzz(out_writer); -} -``` - -确实很简单。但是,每当讨论如何优化 Fizz Buzz 算法时,人们总是忽略一个事实:标准的 Fizz Buzz 问题只需要输出前100个数字的结果。既然输出是固定的,那为什么不直接预先计算出答案,然后输出呢?(由此,我时常认为那些有关优化讨论有些滑稽的。) -我们可以使用相同的 Fizz Buzz 函数来实现这一点。 - -```Zig -pub fn main() !void { - const full_fizzbuzz = comptime init: { - var cw = std.io.countingWriter(std.io.null_writer); - fizzBuzz(cw.writer().any()) catch unreachable; - - var buffer: [cw.bytes_written]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buffer); - fizzBuzz(fbs.writer().any()) catch unreachable; - - break :init buffer; - }; - - const out_writer = std.io.getStdOut().writer().any(); - try out_writer.writeAll(&full_fizzbuzz); -} -``` - -这里的 comptime 关键字表示它后面的代码块将在编译期间运行。此外,该代码块被标记为“init”,以便整个块可以通过之后的 break 语句产出一个值。 - -我们一开始用一个 `null_writer` 来计算写入的字节数(但会丢弃实际写入的字节),以确定总长度。然后再根据该长度创建 `full_fizzbuzz` 数组来保存实际数据。 - -仅对关键部分进行计时,预计算版本的运行速度约快 9 倍。当然,这个例子过于简单,以至于总执行时间受到很多其他因素的影响,但你不难借此明白这其中 comptime 对于性能优化的意味。 - -comptime 和运行时之间有一些小的区别。比如,只有 comptime 可以访问类型为 comptime_int、comptime_float 或 type 的变量。此外,一些函数只有 comptime 参数,这使它们仅限于编译时环境。相对的,只有运行时才能进行系统调用和那些依赖系统调用的函数。如果你的代码不使用这些特性,那么它在编译时和运行时中的表现将是一样的。 - -## 视角3:[程序特化](https://en.wikipedia.org/wiki/Partial_application) - -> 译者注:程序特化(Partial Evaluation)是一种编译优化技术,主要是:在编译期预先计算部分表达式或代码路径,以减少运行时计算开销,提前生成更具体的代码实现。 - -现在我们要进入有趣的部分。 - ->译注:请参考下面的代码和代码后的解释理解这句话。 - -代码求值的一种方式是将输入替换为其运行时值,然后反复将第一个表达式替换为求值形式,直到表达式为基本元素。这在计算机科学理论上下文中很常见,在某些函数式语言中也是如此。作为后续示例的铺垫,我们将使用数组求和来展示这个过程: - - -```Zig -pub fn main() void { - const array: [3]i64 = .{1,2,3}; - var sum: i64 = 0; - - for (array) |value| { - sum += value; - } - // 这可以展开为: - { - const value = array[0]; - sum += value; - } - { - const value = array[1]; - sum += value; - } - { - const value = array[2]; - sum += value; - } - - std.debug.print("array's sum is {d}.\n", .{sum}); -} -``` - -程序特化是一种可以向函数传递部分(但不一定是全部)参数的技术。 在这种情况下,可以对只使用已知值的表达式进行替换。 这样就产生了一个新函数,它只接受仍然未知的参数。 comtime 可以看作是在编译过程中进行的部分求值。 再看一下 sum 结构的例子,我们就会发现: - -```Zig -onst MyStruct = struct { - a: i64, - b: i64, - c: i64, - - fn sumFields(my_struct: MyStruct) i64 { - var sum: i64 = 0; - inline for (comptime std.meta.fieldNames(MyStruct)) |field_name| { - sum += @field(my_struct, field_name); - } - - // 这可以展开为: - { - const field_name = "a"; - sum += @field(my_struct, field_name); - } - { - const field_name = "b"; - sum += @field(my_struct, field_name); - } - { - const field_name = "c"; - sum += @field(my_struct, field_name); - } - // 更进一步,有: - sum += my_struct.a; - sum += my_struct.b; - sum += my_struct.c; - - return sum; - } -}; -``` - -上面的示例是我们手动展开后的示例,但这项工作是由 Zig 的 comptime 完成的。这使得我们可以直接独立而完整地编写出我们要实现的功能,而不需要添加"当你改变 `MyStruct` 的字段时,记得更新 sum 函数"这样的由于依赖于 `MyStruct` 具体字段而预防功能失效的注释。 -基于 comptime 的版本在 `MyStruct` 的任何字段变更时都可以正确地自动处理。 - -## 视角4:Comptime 求值,运行时代码生成 - -这与程序特化(Partial Evaluation)非常相似。这里有两个版本的代码,输入(编译前)和输出(编译后)。输入代码由编译器运行。如果一个语句在编译时是可知的,它就会被直接求值。但是如果一个语句需要某些运行时的值,那么这个语句就会被添加到输出代码中。 - -让我们以数组求和为例来说明这个过程: - -> 输入这一段代码: - -```Zig -const MyStruct = struct { - a: i64, - b: i64, - c: i64, - - fn sumFields(my_struct: MyStruct) i64 { - var sum: i64 = 0; - inline for (comptime std.meta.fieldNames(MyStruct)) |field_name| { - sum += @field(my_struct, field_name); - } - return sum; - } -}; -``` - -> 生成出的代码: - -```Zig -const MyStruct = struct { - a: i64, - b: i64, - c: i64, - - fn sumFields(my_struct: MyStruct) i64 { - var sum: i64 = 0; - sum += my_struct.a; - sum += my_struct.b; - sum += my_struct.c; - return sum; - } -}; -``` - -这实际上是最接近 Zig 编译器处理 comptime 的方式。他们的主要区别在于 Zig 首先解析你的代码的语法,并将其转换为虚拟机的字节码。这个虚拟机的运行方式就是 comptime 的实现方式。这个虚拟机将估量它能处理的所有内容,并为需要运行时处理的内容生成新的字节码(稍后将其转换为机器码)。具有运行时输入的条件语句,如 if 语句,会直接输出两条路径。 - -自然,这样做的后果是死代码永远不会被语义分析。也就是说,一个无效的函数并不总是会在实际被使用之前产出相应的编译错误。(对此你可能需要适应一段时间)然而,这也使得编译更加高效(译注:部分地弥补了 Zig 暂不支持增量编译的缺陷),并允许更自然的外观条件编译,这里没有 `#ifdef` (译注:谢天谢地~)! - -值得注意的是, comptime 在 Zig 的设计中是个很基本的设计, 所有的 Zig 代码都通过这个虚拟机运行,包括没有明显使用 comptime 的函数。 即使是简单的类型名称,如函数参数,实际上也是在 comptime 中评估类型变量的表达式。 这就是上面泛型示例的工作原理。 这也意味着您可以酌情使用更复杂的表达式来计算类型。 - -这样做的另一个后果是,Zig 代码的静态分析要比大多数静态类型语言复杂得多,因为编译器需要运行很大一部分才能确定所有类型。 因此,在 Zig 工具链跟上之前,代码自动补全等编辑工具并不总是能很好地发挥作用。 - -## 视角5:直接生成代码(Textual Code Generation) - -我在文章开头感叹元编程难度。然而,即使在 Zig 中,它仍然是一个强大的工具,在解决某些问题方面也占有一席之地。如果您熟悉这种元编程方法,对 Zig comptime 提供的功能可能会觉得有些残缺。比如, 怎么在写一段代码在运行时能够生成新代码? - -但等等,上一个例子不就是这样吗? 如果你以正确的方式看待问题,写代码的代码和混合运行时代码之间存在着潜在的等价关系。 - -下有两例。第一个是一个元编程的示例,第二个是我们熟悉的 comptime 示例。这两个版本的代码有着相同的逻辑。 - -```Zig -pub fn writeSumFn( - writer: std.io.AnyWriter, - type_name: []const u8, - field_names: [][]const u8, -) !void { - try writer.print("fn sumFields(value: {s}) i64 {{\n", .{type_name}); - try writer.print("var sum: i64 = 0;\n", .{}); - for (field_names) |field_name| { - try writer.print("sum += value.{s};\n", .{field_name}); - } - try writer.print("return sum;\n", .{}); - try writer.print("}}\n", .{}); -} -``` - -注意这里有两个转换: -1. 在生成器中直接运行的代码是 comptime 的一部分 -2. 在生成器执行后输出的代码,成为运行时的一部分 - - -我喜欢这个示例的另一点是,它展示了在 Zig 中使用类型信息作为输入来生成代码是多么简单。这个例子略过了类型名称和字段名称信息的来源。如果你使用其他形式的输入,比如 Zig 提供了 `@embedFile`,你可以像平常一样解析它。 - -回到泛型的例子,有一些值得强调的细微之处: - -```Zig -pub fn writeMyStructOfType( - writer: std.io.AnyWriter, - T: []const u8, -) !void { - try writer.print("const MyStruct_{s} = struct {{\n", .{T}); - try writer.print("a: {s},\n", .{T}); - try writer.print("b: {s},\n", .{T}); - try writer.print("c: {s},\n", .{T}); - - try writer.print("fn sumFields(value: MyStruct_{s}) {s} {{\n", .{T,T}); - try writer.print("var sum: {s} = 0;\n", .{T}); - const fields = [_][]const u8{ "a", "b", "c" }; - for (fields) |field_name| { - try writer.print("sum += value.{s};\n", .{field_name}); - } - try writer.print("return sum;\n", .{}); - try writer.print("}}\n", .{}); - try writer.print("}};\n", .{}); -} -``` - -以上 struct 字段的生成体现了上述两种转换方式,并且将两者混合在了一行中。 字段的类型表达式由生成器/运行时完成,而字段本身则作为运行时代码使用的定义。 - -在 comptime 下,引用类型名称的方式更加直接,可以直接使用函数,而不必将文本拼接成一个在代码生成中保持一致的名称。 - -这种观点有一个例外。 您可以创建字段名称在编译时就已确定的类型,但这样做需要调用一个内置函数,该函数包含一个字段定义列表。 因此,您无法在这些类型上定义方法等声明。 在实践中,这并不会限制代码的表达能力,但确实限制了你可以向其他代码公开哪些类型的 API。 - -与本节相关的是文本宏,如 C 语言中的文本宏。你可以做的大多数正常事情都可以在 comptime 中完成,尽管它们很少采用类似的形式。 不过,文本宏并不能做所有允许做的事情。 例如,你不能决定不喜欢某个 Zig 关键字,然后让宏代替你自己的关键字。 我认为这是一个正确的决定,尽管对于那些习惯了这种能力的人来说,这是一个艰难的过渡。 此外,Zig 参考了半个世纪以来的程序员在这方面的探索,所以它的选择要理智得多。 - -## 结论 - -在阅读 Zig 代码以理解代码行为时,考虑 comptime 并不是必要的。而当编写 comptime 代码时,我通常会将其视为程序特化(Partial Evaluation)的一种形式。然而,如果你知道如何使用不同的元编程方法解决问题,你很可能有能力将其翻译成 comptime 形式。 - -元编程中直接生成代码的方法的存在,就是我全力支持 Zig 风格的 comptime 元编程的原因。尽管,直接生成代码是几乎是最强大的,但是,在阅读和调试时忽略 comptime 的特性的元编程方法确是最简单的。正因如此,我给本文取名为《Zig comptime 棒极了》。 - -## 进一步阅读 - -Zig 并非一个仅仅依赖 comptime 这一特性的语言。你可以在[官方网站](https://ziglang.org/)上了解更多关于 Zig 的信息。 - -在这篇文章中,我多次使用相同的例子来展示不同的转换方式(代码->编译时和运行时),以简化展示的过程。这样做的缺点是,尽管谈论了很多,但实际上我并没有展示太多相关的内容。而[语言参考文档](https://ziglang.org/documentation/0.13.0/)详细介绍了编译时的具体特性。 - -如果您想看到更多示例,我建议您阅读一些 Zig 的标准库代码。以下是一些供有兴趣者参考的链接: - -- [std.debug.print](https://github.com/ziglang/zig/blob/0.13.0/lib/std/fmt.zig#L80) 是一个强大的泛型函数。许多语言在运行时解析它们的格式字符串,并很可能为字符串格式添加了一些特殊的效验器,以尽早捕获错误。而在 Zig 中,格式字符串是在编译时解析的,这样不仅生成了高效的最终代码,还在编译时完成了所有的校验。 -- [ArrayList](https://github.com/ziglang/zig/blob/0.13.0/lib/std/array_list.zig#L25) 是一个实现相对简单但功能齐全的泛型容器。 - -Zig 的函数可以具有几种不同的返回类型。但是,这并不是依赖于编译器中的某些魔法的操作,而只是[典型的 comptime 的应用](https://github.com/ziglang/zig/blob/0.13.0/lib/std/start.zig#L508)。 - -> 如果您希望就本篇文章向我提出意见或更正,请发送电子邮件至 blogcomments@scottredig.com。 -> 译者注:如果觉得翻译有问题,请提 PR 改正: diff --git a/content/post/_index.md b/content/post/_index.md deleted file mode 100644 index 05e8314..0000000 --- a/content/post/_index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: 博客 -type: blog -cascade: - - type: blog - _target: - path: "/**" ---- - -欢迎大家向我们投稿,会同步到微信公众号,投稿方式见[这里](/post/2023/09/05/hello-world/)。 diff --git a/content/post/first-post/fanzine.jpg b/content/post/first-post/fanzine.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9d6e569f9c2dc06fa61d7b2ebb8e3c8fe4c871fe GIT binary patch literal 124852 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�D!G8oJ(CKWoIR z98n{t{HC@lrGqGPO<4%6HNdW}2&P6e8ic^&wVBf zqs~#FkXvKp&evEmsGC^r-tp7rqt-{+=_Jp##<3>6C6N(&e8DKNs1HCO2^&b)CJ3kUYoET_S@(JD-gsF=Ukw z@vf7sb?ssH1AhS@M81{cnfH+-e5=QU@(a%uaPP>SUyfmJ_MtxwXK%AJ5oWP9h77Ma z2Jmw9Pe59xudX)BfbDNKhunVw+E`V_`9JiXlUNNmw7Zve?bp|rSHNbB4amgx%Bu!H z*bIw;dvM);ay9pF<1l^70AejivAlkGRHLF;!vLu2`F0!nexV7ar6@?5t<)w@H2D&+ z3b-tv#iuj8uJ}rC%L(J#Y$zVdC@p-#IBUTtku8->al_4ug>}ObX>jAIz4}~Dz$Jbj zhu5@#g$!b>Hq?NX=i+c-jkd;LF@Te#i~{H*2?5DPqNG!QcpgUH%}FNF>0~Clbak#n z@f&}&?Pi#9PTf@X9VuqU>8@BajxL3Niwg2N4AKWBAU?Vdxfq_1T+a!Jqx2mO%s#s7 z2aLKWt!7#^nDn2i?OU@$ucR*00UiTR+g@7;F6-1iE^!1Fre@*brZu8dmB?3->OkF( zvb(zVAL*j|G+CSWGM+MR2^&<*na=Tf(vTgeE?89eo44G4F0kxA4McKEMRxEY<|X^7 zH9fv7a#k@(QYG44Og2KW^4L|D??_BnE1OKOO4#vLi8^7Tc0c+sH`p1gFO%w3#Q*ff z95T({&>)?Y4=8ER8oz^Oif!QFRa2ku7TC+J$yGW-3dEE~XKZvCxbru_^6SP+iW~c| zYCS{b+(cdAxeMZu*C0T3E~C zmL>mZj&wzq70;hvcVN#Hf?S2G=Inx|F>sBW@INXjt^kV2`bsGT<=r(UV-ik@e*ui6 z+F|j+&X_)>F%{ZQ7z5&*UKDEA-#D=ttFDr($vR`)2-*>q?WS#9GF_x7&Lf3^cRLFl zi5c01eVyfykcYfYyc~o!pZe#w#4AhWPiyo}8-&@-WBg;npreSoFJ12?rY~nTMhrE6 z8~)Ae(CMaPLfsk~2Q@ld>s8`;sI!RSpMzG;A{D(PKDg$ZIZtRW0ftSf4;$%)++RRw zjp2JA!{ydDe|Or$5mtC zwRO)&vS-53$(~}8B#s6oIA)`V_nyyWYg%(b8sdF-Q;>iy&ri4!(T2#-4a)L__z9sH zE$r3m_m`^D8(vz_9vb!V$vl;8ouR1ySlGS-aF#D&wfrQQk5cq^D@uBHPx44erA4j- zVt@~*HgZ|460>nfuN<#QvgKqFu31MFzW$^e$d&2+t!{F0<%Qe=dmg4T~s@;|2 zSo}a+XVgEn+cwIl*H+C?XQ1JTff&X9yF%C=@buq`3VG}t9mR~)L`zo&qx$&bh2-KoD3!yZxO7~eRy~cYMlD7 z<&&0+E@w1%ul!|xb(Q?H)y?H#E=O!_Ri!RdxLgqYDGGU=jdq7fj}aBs&F`!OkI;lU#^Ss`#S;y~`x`a7 zqH3J~;sHo|Xyj8yPrpC{WReyG4)$cFbLULSWYSY=ys=xnX4YQ3TWwn-_1x3#?OR_z zqFAMJ+PhXq^93a#6z2;r)Jp9H_^FO%vbB;*=1?am-Zckw=xg@Q{ zx1E%gla=$tsoEb`ptopmMU%vTTfS#;v(s^yVjL*`X#;u{YjVc7nY83jU)>7AEls5J z^GZK_oJnW0*ptn{z<^97pnp*2v7BtgR-O2z=8w-vp~?~At2k3wIt?~$-0@qL%EAb9 zQ`9=?J%_{DY-5fRIUOP1nK8wFfX-QRNh`@cEo-GFo~$Jz>{P1srlWulqG zgOkcltiM|^R&I2TQ+IA}R7H!zHx1`ZZfbAoM~k(CWO+QW=`Um(pX zBkt~GDCpA?WO*}7_x)hj!ZV;nDrzlvxj5sx$*VfAZx`VzynT2ko_z=Ecn0+L_Z4!} z?9bwkag5_v-5_4VvvbGvaHvJuXkN2KvA zsHwYrtrORst9i|{A8F}CEKO+v(nQA+=K6Jv^chOoyC~4v@qomQ8Mq9(F{9XpT-(zv zNj0;vHq!fLNwh&(qM41=euD7=){_=2Ec2R^$nRN>t}ot-2~gx_(AZzV*(EN%z?DnF{vD>C#XNX~RhmIaKrgf40%nq-Z=Cdr+4qOkdj zOA-BmbzW?ouDow=Ty8&0oUnB#XOU&(3+#9LPz^9vw);+K8*L8QEy?@SUH~^Rv$z#Y zx3D!;N#nUwkF(S>tKj@;ba*6((za_)r1KPw*<5~Rz)0#R`r{ia7KH7>)STBU?5opW zd8n!$T)!=?&t#dJrMj~))9m+~P0>cHb^rY7yRO z*H-HysDkB-dac{9yid17Vh2wn%LJthUcbJ)Z-sckObWqxmFi0vw%1z2V7&i2*T@IY zLEC*%wanXfq&M;2ulb$B;GCB8UL??21F&<`i`zzBCTiFT%+=hu zSudO1tIE5BHHU?v0z}>S$PSF0@=PKc(2PAEDXIYxif!e+J8bRm=q`v$HK*t-6laXQ z+(RuB>1!k2Y??<*IMV;UtS!pbSA#=$>6U3f2rbaK9`AZ~s6!kiwA^aM^4_TmUldS_ zc?$3RULaBy6k_acjo9Fr#RcKI$Fq-*PvbsjWY_KP+x6FaJXT{@GW>w$oh%yy7qvwW z#L;npfb4qmQoe7Je$i%1ozT9`t*NE1;?cdpk9k+FHUXyur7$RuhNEvzhiLKQO`vFd z@%+EM=j!IHy)UEYf6X{Z@w4O5-KPn!9f}38uQ+TzWcT#{@b#8Kadko0=mbIp4W0nO z-QAtw5Q4i7?(P!YA-KEC;0*5W?iO@#8{G2E^L}sLx_|Divwuw0`7_mh_U`W8Ypwp- zGO1YQ#6fDKc9(Z?a)UfA2Pre{=)FY}ZxZ+%)UevdMJw!I@IZ<~bB6S-Phq--Pd|?!xmu`^tb3p{g@K*T2mB zSTm!NS_%Es~~qu17*ds>7rHAeiE*ux3`#=7x+z`~Mx zu#1`H87U>Ug3^~+7ioETkO~F3=;vmW`Yz6sxCgup?eeM&YIb_;Lzc>Lt$d$L^v~vC z0qgbsDqzL;bsY# z5qm%Pkp5b8%(!jUH}B&EGFt=NJQyqzR95%joc|_{u;&Xz0b_rky_vdxCCtl^Xs3Z) z@mF}MsWWqO;t#X!gPOV|n&xWX*oU|=B15)vxAXd|T1+1JUiplT4?^alo|Ff(7&P_L zuI9cQ1MRw$JXRt|wos?nAngK`Q7tg&46E#eHs-wF!jhjzaDTu3YM0`?ZE-PrT4Agi zG3!BK0@ue}^qZ-pMx)zFhbcUh>_p3XJ-^G2R+<78dbCMZ<@3#Np=Ek%k}#-C=ndV^ zHmA|+gU0+Q=lQHe*Sy}hY5bI%$0*SAG7P2Hbp`scPBcHiArp1ktNs*m~v}x z#O|D+VLJt$ik9Ds-K`3_d}_P1GHGzXX8;d1j=v^Jxu$*D6l$oRRR24R{5KsIOLYF36*#RryabJmi*Llx6h0)q6~}Z+hQR3x;=@8y1KcNBV~p0Sg;nTov(+Q zzR{;muPC9|8bDU5a2eJW^LnnSJc~PL59|5MwK;{?VmqNa&(-7s7q_D&)*P)f>x^*nGmfY7xITN6 zcdYh|#6}s2HH%Ybh`L<#v$RZj+aF8YVJw8{MBq7T_Nlcqcs$ZRwB zU8N`O2+_-UrlNd+Jp0w0yKG_q0B&44D?FnuF9+Clql|Di+ZoZsqGLclhuKY_5+yn7P5Q$iaI({5}D^Er#NnT?i974EKYO3K5@(m*c!Vq8qUg( zztLzxZTzE(5hIG$Nn^VdiAFVLrDxb5aTd;Yui#SrBz{#K1!XldDr^J8W)v9?gs`8i z_O3-HV?ika3}|X%J9~qY^P57pokGr80+ZNy8=Wd~cZ(PCwePoQq<$LsGVB&VbUlM! znz~lx(5s_k79mxUNv?ESP*TUpdeDWadsS0Y;_q^PkK=L`a4p?OD`g&f)~6l;`s0HH z^#Ka7tR)TNnTzw+Bn|?N%I1wl_XSHCS8zt&J;!P{6QXCvgKye4dQP?`dS(q!)FB-) z2(Nr~SCOU%vB|P^dW}GA2p4VqG6pjzGq&Wr!uo0cQs(AAfJM!81xr;#pcK;Vs<5uJ zMY&dGn*WD|<)vJFCSp_$t`9ZA#bZuCZ34Ay9~$A)qAmZhEPj+_#fuw4#3vt0Y`MG3 zV|VaO`QaJThBTA%nD#w=bkpT1bV%JOiZ3BAD(l0~kjGQbgq9rUd9 zfyk&Xj6b}9aS2s+nT7MlrIW5Gm}{MKj^VMXllqw7Sh9G=c)YJic5eq)$@n>loVGjP z7W|^~d5w4+HqqKWX=gZ|3X$^$s;b?8=P?<@q}bQBb{=}2xSrnM{w+#d&{UL|vaPJ+ zSmusM#}zEm10@$oRn1qHs#e*J|El~D?Ed<}$iL5eC*h)SWP@#Z4QDJaJ16Jq#bKsg z2IDJ&zS@eLda+l0T=r=(3s}iojW20G-E!bgkeP~~!)a}9%34;Sh#-qs^EBM}FmcRW zJu?g7Xmyot*J{7@4`A6f-^wmke(zG7q(x6wF0K(JCHGG~Q>Uwv=Ny!O zfV+wpcWGhQ_~nwBjxJ>~P&n*&WRS8BT z-iv39i2#9y*G>nm@g9cf#NWv|FE^8tS2q{;qn3zVKS)xP0(1S`bW0MwfF<2?U`9;a zRQgp|f6;0CnCNV1)+`Y#5u3I=q^mSnpkWAdir8}4n_iF3cPwIFM9quC;b5soqlV^% zt1mk3#6m#nA?^xy@x3#^-XypOX#4TqYN|1L2|Q8Mig>kNzs zlwqCX<+-$1V@>lhUw>cYiIhAHxvY*f1H}(Lt-z6Qia{Yh`JRMXD5uqo5QGHr)_qxj zA0j1OZ^KxlHfV&<0CeWv-!y3lLFj@^XA2ejCw&i#Q07Ed=Nr^Mo2Fe;>1hYat~by* zB1%>M9nk1vXutWT$jp!Vm*muwiiU+gbY2Zo7d7iM7h3uc(9&w`UmvSNyttO#zQ!S* zeWt{*uu(n{9l4%`(|%n(6gb!MU3;;O#>FYyy9C?&c!Ejy_j2iv#Rfg)nX301G>jPb z)Q5(%wK6lKI&<|6C!ZTkqyFGX|A$8bI@Z)i_fd?ruX^l*rUZFu%qt%k4+X_0}>R)*0|aK7I825c6>RSHl=`~c*&vB* z8_)8uNOIAZ_3eW?d|%54EN^wdrUexKkC|a(i}MYOGrr1WQ3pl(48<#W0Tan{jZR>I z4r-<4yBZenG*}qzwUg>Pyyd!g%J~dd^K+gWf&@8aqYtV6Jm{&DO6qKVTUfOZ)kKe0 z{%TmkSF8SQrOrD;?h3+@zPZq(avfXsDW#azsFryp_txM~HKHC3BVJ{iqVQ3}KLD9w zGr6)ZX{WU^M&(lgo!pWK0o2cfPNva~bNE}+r>vhZ878hH=ySTS2U>IK2(q{p0-{!r zS~cj-&7K9%{j-fq+ByebUQY3WirJe>+~O@$wsr!bNxj+s>KnY!`c>U23oj~_Q!_A6 z_WFS;P6@jVmMwuQH;rBdvsLU@%HK$WwLt%6f7^W=F#WkIHbvv`+dienb+c!=-{iSAU-IOBeT((~ z{-|16vOdW?hBXSOc-}e{GwT!0s3%kZf1U`4oZ0`^Lp`aO#nM;Imh89<1GZN9auUJl zXr$=>(b4{6qWwRTZSTII6SFW2!;y$!82)njANm>Wwa~kN0E`G)^utCR&q(^ZF@awE z!HCfsbID=Jo+qJG@17KP6-KmgmA`I$Xa$K#BqHhWbS!aX2uKK7u9;Tyhw?^)w7ABNMG3bV`zXlM2Ru z)m&lT5-Ai9jX8!QiQPwv;!KQA&nqQ{^SJ2dv^m3_@Mrw?7YQzZe1cXhy( zt~OJ+6}VRGtWvE`p=lopA?XIIOoqDZyAzFEEsmy13XYyE-Gt1cI&IRJ$^AW3d(k~% zzq$yt?ui2%OHmw~W1&Hd(Yo$nXOPWXHv9sc)-Kv!#by8-iFQ|$+XOFCAjYozU?;e_ zrK~C;fx$gC()sxe(V_&>`{b88u}6Hm&jh)C%SIxyJxNu}KZ_U49SXjJj$Kg0psA&aKq6l_}W{G2|1s!CYXKb{TX0VB`n0kqnGx2~!oD#G6v3YcwTj&L+XnR9&=uf>Vkc$yNYt{FB1FMYke?S(OK3@gF! zOb=E;VoDa=l56LGKpNy?byuT~^LjM4j zOUuke@ppXB%p0F%Ngfy~-R4Gd1n2=)C9H&skh`p@@ zC%%ookqVu1P@b#me4N#_D6#;W=(^3*Y9PJV2O`zt*o?Ur7GkBImk-MgBKxA*F zLHa9<_t%rCPW_6#b4k)gu&3Grna3oH7pzw>oXsGz<1OP6vlzj}=bx+>cOs<@L`96` zyk07ac!A34AX%a{jC@O}$k6UqKHro6K8O$RPvNAcnxfLO8~GvvjQ zNn!ux*e;KnWOcicKo84UqWcs4hy2csq3zLpn?n5REK=j4lDZm&2&opKl9NjNW|#BR z|NFgF2Ro%ToL{hONzy~o&XDkSOnKf-#xRHjYkTf1ZDeUJJKv6_F((W?TR>Mcc2t#b z6VfC%sZE`Kd}bxB>xjgqL3wr~3WGI%d-j4Z4br1MRu1jDXVsxOhESkL+8#B%!%k?1 zxurBmb4`;Er4G6SQIaIy>g*P7D>y}!QB8>5sURqYF5CNjc5Yf*4Jttdi> z!}IP-`M|mczA+!Ck=EW&H(-7>&DCgagrk?y$_p%bYH4e`RYX)kt$|B%ad8@!zx})c zsRk1B%a)#A$AmvbcNTPQC@s)oz_`q@e2#P}^R5A$-gv9YQqk3MQJFn26{#!i%Z?l7 z;?iFea4^*F7BftNy(`rYWvMw5eS;-A83B^r%B?Z)YXn=fc$oz+=9(}Yt zshl{zp%ubt6~3ce0=3MhoR<}uzw-#VSoDSiX9X-zC>aXQp9HaL&*=Pv{Kan@W&Qy? z&Q3vDo-{&TFt{yNF{uo)XmPqUp@eGl9+T3fL?%4s76ZKyh!R4q=yRf4McBEycC6s{@>>yXIlIOf_(sbufNtg9Xluxs)bJ*9%R13kK`j$g-KRPA zMSx!3tfAyiJwEH}jbR8lD9ye~=kYE&TZmH-!_$Q$v0h(xU9l5ZP*rtZpxw?_)d^m4 z`wQ^;HvFO;PU$aS&j8`*1a8!1DL=|nK7cRO5ZNwEFOlhjG(C~jhF-BBd~AyRo~Gtg zp`RGEm2L2USUB{B*ArG5B@4t7#F1s9g`#yf*~Rc}t1qOn=C;)eio@qUk|htAD2X*je?6f0(T8fY=eVT5y@zq` zttWgLM$4`yi@poXtP*vo6~{l~a9-wNNl1j5fc_|hdU|d8ip1d8M=%@`wz7#->7;2% z<9v$?!=(*EmN@FGul3imXwjZ6E`(|o2L-Uv2}{({{(P3MPWm zU+JJLt-kG?su=k8SOW_H!$?I8?KcBAU z@O^qe9ZhB{bp8XN4uoKKqar-!Y{~enwCXJ1V*U6*iSD>T{bN~2vd{G81m1&c@9T+@ zj0njGI@QCh%&?|pF+Z-QpY#sb29`p-g4qAsZ+S@ah8P2J_!A4`z=>VV<3SAI<3iHZuoDPehMp6GGYD z=ZA%T7$wj~P1ICqqy$_wEGqMfc&`7=5#Lpeh`HrqaeCO=e)Ww<5glOcvQb7QJMSl* z%puz2&M18eT1N`@WuLO6qUkT$UjgUQkJj!zttGXou|re0v2cr3|JRJ)LjMXe#zg6ZEaqg_w_^6xHc}i zlz8S6`9ThhSXinJJj#PU7-~r#gEDvG{x5%^eFyR;MLtw$NsZM?(xmaTNCa_VxOo%E)6W<>_!18f1~pX1=k!R3Pdv+9?UA z)>4)vX(yTs@pfwLhHhDsz)L|UUoD?i(q3U10dE?qtOR;=hsryHiX)aiT5dzp8cM|y zwx<~n-;IBORbG`yLTlsh&hjVD(YB^vqXN8h)YQ-UhK77aAWTu<&QE^E=c(ORu_RWj0H*q%Hrrg3_QK<`q zW3UT5lw+@8)r};*#Z~Pe+xmnPu}?IYPuIE7-~IgJI!d3BkAYK&+DNJ;KfkPtzLfRS z)YbMXFgrco5t5`B+ddL#fc^>=j7uiO)d}1cTR*JcdK4l}J8|so#J#v%jSOHsKs3hl z#82r1d8aHA=B*+$Kfl04{7!&sE7N0}A5?vcwf)HbvcAvKqMAWD%-_*=nji9}YjT#L z@XRsrf1C`e-5jFBb9>Hhq<%hDhsb57TeC}U83_8(ZpvBDvXDq-u9tJ)%ha~?Rk<|e zywxjE6MPb8SApZK0*$-tjJgcE6h&}s@C*}wQ}keQ zGsNzp5%K!nP;6#OR&7*{R8K%!5bP{ct@l6H`v)M^7))t>ARJiN%u?ro?25!#0tfz( zTm8Tg-R?L$&wU?Pa=rD_`ExdJg1N9L64>V-png@F6F#j>5;=q8@_aJL<<&yjIM$K< z*hfV+UgKB+h(iI?S&z~Ce(T4MoFtjgtM+Ljt1D42EYViQiD!}L;jBW&@lH_#E#q)? zBY@%HoD8z2ydf%dx!oM(2B+P=$U1~pM5iSB*`R&G=Hny7*=YVBPh=EaJl(DPvr^K1 zBUlllNuEk<=!MWyO`hrJeK3Fgsf}62v3Ez3_a+xePG%Zf;-Yz-ANL5etCPB5Y6n9VpxbRx_xk1;`RlSco#cHm!o8!V zq@oP;w3i2$d6Y?kaAH3#Wuj>?O_yAckl=^@(na1MD@XnlVZPw+WrJemR|j6MLWd=N zFF~sx%6d16x?+tB_~#u@t?eaU=T}qS#{_v^$eauix`*%2&g;NhzE)4=vr;TRGiepp zGYD^HODlSM5>C^vvS@roq~u}UHQB3e+rpS{P3p&|2GX zEh(6KoTk*C$m6wDn%IOUDAqRbS=zWI-)h%0j`+{Ldr-~$Q=WNdyP34N?L}y;p?Z*} z-hyy^N~qAk@DCtYi*WLtzj{n${-!uy%nj&hNpkQ0nmA2Ef;?b%l1D%n>?K`kzqfC{ zId8n0R`9XP)?D&SUBt@nl_i~>0KwXtwn!67a@u{lVru-){N}Nd9UBp{U*$aSM7gf3 zP)YEv@L_Zsy_czdrB9k;V&yv{k4IALsm{eVjSjlMbzVEmH>`(7@k+4Fyy(Q!55x6r>ojgTnpkeCJ<~{sE5pT&-SvR#LH3 zP~Emi8wP|n^zL2a5L?U$W4uUm3%>1SXrc8s=NhW`F{I$tcF zehvN3TC}?TY^+yH?kk*L21hR{+b~NDDO}li$%(=?yRY*vhlQWWLNnl@FXdDos}fLp z30=!iBP~d`ObusBtVF_^A6r3n1IFh%WKd9^F=`~G+o4xmL!R3%FqFwQiJ+7oWG@<3 ztp9=LeCQRLyhWWM53cO5i<(Tr`t{C#;I>w-dAr|upKhHAf_FX!XZUlbYm$_K^jieW_3 zSnb1NBS&A~S7^@tWS&M^48`QrV$VYcME&?6FU+z4{T60l8epbDoC+?i%s;Ao)-BZ2Y6Y}oOl1z$TCf;zZ@gQ_GY0sB z0inBoIWaWiUN>^>IJeX0^Ch_~jgkHu#Zr+w@u295xI(6jUh`Zggz0VR5j@=0CZhZ% zltlby{Bpc1)l#7HOz``4p{pR;fMq4bYqy8oT1aAU(no|_6GU~yU~)tgpRU7Vs*H^eNAEz2(N z_Ls&yZH(A4>q^=@e2|rqjb`-B+cC1XSMsHP*ji~x1ocDW8XDVD-z}Er{^a7APU&cc z2SL!7@`k)AMc@{HAS3v)@ngKK!Np^aDpnK?J&WE7-a&^`P1nA|MTsMX|F)4Is0d;m z9xn66cgl4k`>FczuMl2+Cl=fu^ww?Kw9hG9m1tK#npkZNrHUQe@=UW){ONnm2g*l% z1ryFE?Y^L-=iXJ7`iXuhM-5dzf-SJ!iy1fJ0)4g;Ayh;=!?M}`;iaQX6{X>iH;UxW z7U9f;`ai(D>*@3p3sb{VQOsL~kF0A^-$dirng&Y!cL3Jlsgh+vz%AzzFV-;lMT(ltU&1b@?Ga34dMT;JSM` zif2}UIt#VGI#+PRiqiLrNe%nUlLwnoY3=ngH{{ zzxL+T(<)@iXj#@Nlj5SFT~Be|^52%4j@#e#K?JolBh%N#OiYPFUYt$#uyn_-43@yf z?bABMwpkxj-#Iy}P0Zta9%dDwrq+~A-h(zL!UDV@J4a(nGRCcuriF)=*nMGJhzZ$v z*%&HPEya4Vv|k_THfa5K%DRNc3Cj)kq_&@w;C?x?Go3<=<6{KqRwmDVWeek zrm1w;XeNbpI&&!D7|im_+&Z3=G9T&W>;CX$*h@L zbRYQ45#uW0GVb}BJ$j9|J<{FPwe_pvhfxzS8Y+HG#Uy6Ki zpz%Q9Q9X%x*;5x#Bxa)Qg@UG-655+oY}oC1?04WFiPUz5rulMsE`H8ndtOpR+tDl@ z;wIRoYR}NdIM~3T(ifa0N-g3E^flG)wjnv9|D?`-3?FqvF~GmCH<+bA7`A6Jxfdvg&U-QdGIs54%e~tB!50Xh276E z4fseFoK~Rl@fT>^%s*2XvRpZsy4Qd2Ew(A^`3>0@^sYb_9?3e-f4preOLpd0?+X=X zP+MxO)v>>Pc$FKW>dcS4;ONjfG48>X3+0~n01d1D16&Bo?yB=nzt7~t#hQi2OZxc% z9rUhQE~kEuWzkYRmtAq7Rqw8L5~*#J*r$L?iVV{1!Hslu<%a(NC+{K3r1-x*r#tEV z7#l*Q-zO^-fs8y=G|&=O;gN`boczdblX*nLlzhvJ(X@wFK#yc9tg>5vrC6B8&>7)C zJCfZ0WZJ}i+u`gu$~RC=CFs%$zYDa$)%ma_$bM4a7a*=^;$W-ktJ;0m*3n~&`3TEr zJe2zvarQhAS^5jUkg+{_(Z!A{A7r+jZ9S={6AH6UDeFmwfA!hoVw(4&`!Hia)Q%}r z;e}j()+9FIkfQ6k#+ydI3Fl0%wX}a+oM4V?f=e5^wX~%K9)Od?(8lEQa}l1y`7@E} z@zSD99x&O0eyg!si@?ofwZ=H0BHMV2mu{RS!#$6eZO4<0ER3qrP`c?u))JK?ZUalj z*k`U}q78{yh}>d2YGB~l-4(;d*Ho{8*G7glbZv$+%g|SS!fCJ2zL((OJnKdSmy3#` zO9X)at}@Zy;~cX#or!NrmMo28xD=wN>c($)Egia0=OHA0UV0bt)`(>4>=DDmdnGB6 zubKXT5sVBz!A?&-)o+@JhFk)-{s~{wt~C6EI7$bw>>{;SlfcEQU1Zs=U|s6tInwQM z2lQRx-p~(CI8$pE#hTeCQ$jwkNhZ*njz&VkQRChUBC<%WHYv>vJ+Z+`0DN0bLR#bH z7As99_xwtt)8(y-0{tTTQEc^#_LlgrB73W@^W6;cGUhp@is&9H7$VoSZDS6@N*Hcv zWJMAdzA9q)dwdgre1n4e6+LjayC|mKyxu7r@hVHR;keGHuP(YkYeGI=zNuu8P`NpD z_RSSh&eU^k`*FTJNBc0D;>6!2@jTp#ojl}}wNXW)3aPS(s&SX2>G$7Nhc0SN|DIS3 z6ugsYDr6Nbk1JSZ7N1=*RE*A`{N{imm;K#i4(C~N>Cvlf@55fE<_&*2&=#(nL6)d+ z)czsraCzO4z0TjU<7Sq#(vo{^Jm$g28AwI*u`}mulb8)^4(J^nX4aM7%CyGlmU5)5 z?;CdL=NJ*Q_fdg*+0rtZJj|~GF8E^Byy(Ak(8iX;e0vo5@Sj3mkmVzkB+S^JC))$| zP_7~Cp1wv&Ss6GOmCF}7WFlzkKff$NCToTc(7ld;&kysq_x)YYizo@k?@>*DWAD$T z`5xCa)!p8Gd=7=^_jRW1t3T1_y1V`^m*#yp8ScKUvApj3&6uwlclN-WS{+4hU&@*E zjq3~&h*2FbR5ZaJk!kHNd@%p;n@{sPxHUVmMrxciXzR;Ti!XaMRueAx!&0q?K1;;@ z)V*IBn5S_=4dEdt+T@q#8&#t>AGvDWuUx!ni_Bo>Z^>dhZP;^^$|n{Ox?4uE&KcwOOjw$3+9fn}xCGwci7zfz*Y|MyJxalVTBhEM?B#*OkNQk;|x| zDT5)XREm)}?N4)X$$CwTA-CrUzxTIu>Q`i3tqiA-9Gmw~CNsX!VKU-Jb@*5jG&vk; zXiCy;Ul#r#ROnqiN-2$qbJz~ch`KnxgKTM5u`NK)enBv`*{t+y3#~PPBDRb!Y1-N( zQ+0}=Z`)%_qJum0g*Ek(4!)Z6%)Q_cl0JvDGD?8$ZsWz20G(U?P{0Y$PqMeBbbOD_ z%<8rA`gRqZx`UWE+anBlZZdO`j2T@V92(49-H8n({N&(N&DCY3Kjq8$6p4pl2^1Po=jsX;IeEyQ*_Ql7*?Zeqn&CKJp7-kfOi)jr zH#Z5-fBf|>HR1DLJ;WZOm8AZ6`x`5*@>yJqWS-);c`%;m!^jf0;Xs1V!p^PQFvX5K zm&@bFMeH8XdCsmNq@Nu;)1HMF zcIr&}04~d5WQ4`VDYj=Kybm6DPpf@o&--d>3~5u7P1@<|u_40liA3fd(u8vj6*vpO z5=Q5JW9v`6k`JtKW=R2ipN1jVqxHm!W}NV>^0ucvKVzIvskBxRKo$(=A0cau@V1;> zaC>GT8?+V`S8Doz4d=Xi>$9HFQEqLthnEuz(Fc-HwFedZt&wUR^rg-?vae}%*e{Za zFZ}G0L44HERJW}_7`bEzg{Ng zOre|TSCD;Ei7Dc;rq7d~RQ*`J<0by2Mu{t0q~Ko?Xl2r`j~!{auT6NiTp62pJ_$Ba zv6)_YM1~gk`g}n%xn{g*6YumDqBYW|$xmwz_i%ibS^YDy=GTpD+Z9 zeQAk?wUjXcz3@0(bF^k&M%ELZ6_TUk_Yo%FK-ZnAoYq8SX&DZfC&?1b-SANiUuIE{%Cua2R5zE7>r(NQG9E z?0u6v+%xpc{A)qK#_CJ(_c`hQ<(T&n$s-a4;lYXX+${;@W>D|F5TX(5s8|+54K}d1 zl9fvK`RHa-U$F6w0y*?^I`hd^Uy0FZazF31ZZUK2lhvbA=ZINn=!_tH(Gn7R>d`ND zv6zR?hrF|Y6?+m5UXOM6S7!@@p2P1W)Cp0xSLoN2YZ)Csj}>7_H;v6_thFXBsT%a& zxOp&ELPYfYWlyozDj5UCyfBZec`gG72vbTaCAQrYw~8C1)w&ZVZ|k36=Zlx>E~m+C zC70Dpk5lFCf#niBsncbNIy^eAY=k7N`wEIzQ|tgAX{|)~3{EZwYH(DiXe;rT`43N6 zP~i{;dY9#QSGW(Ge(2(rLWwR!w7Dod*r1Qe%{WI`^DZ?sh#uB9w$+>Kj4 z(Fdpj?(yQe$AV&mbgOr-KnbR5M$C5dQ+oP%=|VrEoF^R-nPklR0ooa6Wt7!ze;OxM zdqRZXc~YQa^u43%Eb{hdfn2MG_vnnIZ)Y?&-gI(*=P2&~nUAbIeRo@Ifc$2^LYTpTEM1Pxv=7 z&eU;I>#T0jAy)vG>jv3Ae$h{&zk~#axjSJMOZ*9&B>%zL6`r4LDxABc=sm$UY+oK3 zZ)MTr%<{*2s|(M`YU7(gLG3}706tRYq4iaq0rLTC8s)B@Vxp zf7=mPriZ`jSzLjfC`))d%}gS{&0&tqd-wc)j^uT-CDDVW(F0K21)k5(Z!Fat2`nN_ zj&?jHC+b_l=LUA-DdsVKS%;}Q+yq090G}fSA^u-i*tS1l@Nl>jYvL{X3e%VG1@%^` zQFA#i)rukwodzmz!e`OSJjk4x>l%AKjru8xITN*1e)8_hnMo6S<9JVs>8JBFrQw8PFRKR(fWk+scim|tfv65b#-3~(2 zCyhfK+>$@T}C%az&`iUKNurB)Q(Hhs3Hp|b_8>#IXk zO{0Y|I$x@0(k0%FghYLm>7!jqJowObQ7nsI&l_ww_t+tMG6YMnGmsXq00#Vji%&aA z!z49v6Ht8$^>r=eGM3Eub~c{4L;dJ0jAOV9xj&p5*?-;YjLJ~TYaV0OEQOW(Bx^+j z_(-TZ%BC+uf-~D|cIB8eDWOXebiY^2E-Jp;qbu^ujakc%;|D6X;diBPvdbKvL@tRW zE(dx7*1Y-mPqro4z2!c4YNB40qE*wbDD&k~Jg>qU<@++v<|gv?(NYk_S5)f3aS4VU zFh<9H(}|={q;HvVTyT}(ZMiwc#t`XH@QNf>_J`VlI@zGX{_3om7aQ579HWU4eyd_l z#nFw8rW)JbB9T3yBK{72%KP~>waI8aiWsQeQ^Zi4)X36D!ME-Uesf-riBs%-)3o5m zzR(Sj50Pm!*-D>22}DV7iY&aorKTo-vcLAgw}t|5h$YEH#`YnWQWnj|dOv!i)2E=f zt+V}yqk+$BoB=V=eis5QLnl-1AsKfX){9~|7_k;)uM7SfSI;@Q@jF@~$j8QS82ZDK z!;G^q8b>lU06WnTW#&}^?4mr-&f9p!y?EP_fNaF(B6Y2LQRO?O-Z- z>}E62v-VI7Z-$JHw!%+B`m#7@SP4k#RB<73M4c)bYO~C?(NXTUs@k(0-Ui|6WPGnY`Bk3=RNrUUQKDTvxV!d@b^l5qr^Ztci%|gXGd7jzMnH&sW_5 zm7ZIYstZBxLl>nN$K}nk`fu0vh#i~Ux=Ig(oQ$B`SebsDbhQxBLLErJ&>LSI$jSVW zqxTP>OnY6v_%Y1sBEMY;dsjjasH6HG%8{S=Xx|yQdUrejOsNoXR?pJhRM*s#lqI}l zo8zp^INDa@=pn#$C6KYuB+4*a)3&R8xJ_Bi?7`uN`ca0mIr!BlG|bq}w$Z%(^aoOg zKHUr-TT3il&HLMGpK?BA=+JdF1%B7I_M$eM=PC8)0^_dE=w*iB7q89+KDmVa3N|Fx zo!am7-c?uHEPK6(t*ov0_z`9+m9h6cT>D}sYZrbTvQa&U=F11#WG5wTe>jVRQ%2## zu8g;6<)*MS-DqB)59?Wgi(tc-z?xsDOHEzJ()G3D+|G~l%#nr>#2*c6rg9#I<4m{! zbJFw-)N#y=zCh^9$&pF(bWoZ{cCFT3n)LTPs(2t5hf~H!ib3c3*>Gj?s>{%H|@^3T>**OV%X3KL_U{W`> z6NqO^BTPaNF(C-HQ0iArE%_*{ z9lpMXe0h0)$FYoZxOD#dx$n{@&|J$PCA zr2o*&3tsq1%v|n$Vs7X(=mN@kvbsDa+H-R~FNv0|PCs_OIt6~eKTBq6?%05U=X)BJ zfl~6#4U@^;J2%nC^67FVO&>*Fnkv-@#xGF>Z+rw)3D$lDyLn@xS6?@ccK&IXcpx9$ z3hn@I>r36Vk9d0!>1ygGr7_F+KJ4GZZP9HurHX7gXm=$^#1D^3ahhmDq+15setcp% z5he?v-Cjpk7rMGdGtC~YrDaH^N(pAo5&Io|d`tXP$D3ZwtX=Pc69hn4^~UmO9t>Bt+=-X^$! zh~j2wjNRd3YVY(pVpKr18ebkOGtF3tS9kZL`5@e4BlLiGR&pW+Z`CMmS4Bd%yD?+1 z(3PBTk}epS|}RQqf6qI0yRy9PMP zd%Z4M5xI8K{0m%4*%#HJuR9$=WvuuIcv+g=Ss9uox24>R9(Tt>`!Y~J%u3{eTCqd3 z_`E!+NyOvl`V^VUbu`w5#mb5~m-Qiav$q>{3_fj4TbYr<&_`YD_VdxjsW+pDgXtX0 zj|8Xjbc>&}&B3tX>;^imbTSGn=+j5xL#M)vQu}aM9PNmVMbPRao?;rEIoQ3&q%21I zPTX5u^ukn|y zEbErswYKNiw8SU1Fw$XyIV zRN1DIDg5}o(B%!Ld@T)F{m~}e9`|le+w=CS)J&~EuX!$+X(V(qO`6I$Rfo`Z&szkv zsn*Gac0Kf{Zl(W!X!`1?Ho7lbCE5%((vEuIT6xZPH?v}Q=YtZ6e2u^S*?i43L zpt!qhU%uaa?~lpMO4gb+xpQakIs5FhHx_O$XZ{5*AqfS@uh@o`<+ah><)Zt2he3T( zUVWLIenGGBea7D5Yz`}5aJpOQEM2>FN<~0Q^jeYqB?gH|RWbNPUFEk6-$R_C_6~E9 zJ9#_b_2R~oklTpp9x4tTApRU-zdo*)W)7JGW1K|CWuK0&bFANpOGHAxsioUCs0_Os zPn=F!rDr_-LjapuS(zoF9+LSDZegsHd>n8pvfh-XNcIT~@}iDJ_+-ot5x?`lIu^;$ zk=I=#Hv-a&qn5O_dQGE`EPf!X9@~ za_QU+uuQRbkf%a-TI5h|H1`Gg4AA@p&$O_&LiDb0re}?fvey-9f$EPTzF{tNI`BJo zOhFC-V>cXef{3DBb91VaAZR=~`*t>A;u$dsFSeB!?7Xyx&N~aD*t&Md9nM;Jq-xAb zBnH`0ElpDf5^={}wq2=Hoqbsje4csYCjHy?I6h?$}FSu&5B=g zgR&*_;iz9Nz!;YOXi@9yeSAwexd%14O_Q)K-Xz$W^R^)Z#US@ig(5B#^?W^Xgr}9k zMaQ#wwA2Hno&_wt`m)W}l^>@$8FvHR`rVv__#4>PzMANe@3#>mM2lTd`>YzrIRCt$ zK>qv*qCefH@;&hA({O<$=vx=_j(oe!=FDC@n1+yUqyN@~@0Mwd%O%E=&ju<-){?ts ztHItJMEv%gjAqB#<-9Unvbit%L5CE4=D^r#K?S(LNYHj$gUxlzt<`(9U3ksrutWRK zue_S}E9OW^1_EpBiI#rdytyrk`nhyzo2SC)69Bo!Z4O z#j?^Nwcq*MW+^-2H=JJV>59bV(ViD5(AlE1_jj1b!pR~ei9NTDi~kS?g-|1$r>3~h zA7ewN$P~7qQD{1c%x{4BZPU*Ge$gqTmRkzd!#{^@YXwFpa{b#iy& z_y!;##H{g(_ry1`o;Z(}ePPoeFWa}pvbQ3dXkBT`$+nL0cKa#gs1@HhU=a?8p=2Ih z*h}xN-sFa^4aI*o;uP`wqoDl0HOU6B4npck!QofrNhfqIhXj?RO#G(Z>=q7tNwq+~ z1Q5u3j3zVuP3HfzCQFjC<^*!Fe3VY(leViywrDDf?_pC~XN!}sG$ZvvI?FfaQ}Y8g z+kiBwW+$5}5OO6*$*8twpy~=;>*QIIyK6jcdRh84j(kk05nNk_FF_EVYwMm(8LSbOSdPocY-P~(U&k{*w}`AF2!{+DHhXt6Cl zTD^lP(|8NZ-yq!{2GZuQ8TT)z8Dh=<>ErgA%*3~&v`nvvs z9wp|Z2}m$#=zebf;68pLgICV`zhUNwMu$6?H{r9S9#ANpNi~f$ zOq)UtvY6pW#x7^UsN5bFxc8YGYj87bvExR2oZpLUE;`*`2NBI^d|Xe+&(ZtgcYJ@M}dGNGC1B>1DE~q9qAMWN@>Osqe=&Y4VM=%v8?q~g_ z%k%Q$#M=I6IVE4M_HkbgUP4xdGTnZoaNLrJpwBa(Qip;I|GxH=Pma>*JdJx5-rq*O zO{^6((^!oNGm!BfqpBhl!!x-aVX;KuFNV83IA~-KyY%`t@a+X?N)o8Ir9` z(Q&Dce)Duw6$>JXe`|m(B5tHLSD^5m-F<{ntD8(Y4YqY?Y;uEQ|1GTOy zf5psAOtArVC+__dwx? zID{gtN>DwT-zRo;qN?edKhlA8d~3!z7wL%G;iFlmE3sfr#AFkBCX(cZB8Occ*yXL9 z&s`LP4h`PKKx(u~+X6NnJ`mzTe|v zj2Vi*-2kYOFVRVP6R~f^=l1L~-wLVOF5=g*vRN7MF5{iKh(Bo84qI7xTl3xOOtK8& z&&AA=6=}GUX1#AC8!+{-*l(5+S0R7`JBf4*F2BA!^&ra!r|*!)pAaLNknq#l3Dy}3 zEKqxx`HYPpWKWNP+jPToi}lb&<#C0$o=sS6l?DsxXP@B99xraFsp)(0+d zP%~N9eEEzFIHSeRp&abY7W*&A=)!-WtyDWG-kY{@!HxdHB}75(@WyGT`*nAryAm8k z&DZDsymaJm={k(|4$EgepqxN%=COP5hp>t}Tcua?WU^5YgTZ2Jspd!D!TgY?UFLUs zCb(VZFJc^emOt2?Ijln3da1yU3oqTeEQPg)vAEnf(zomc;N@oSu0N4H=}a#26AJma z9}&<7`ksCB`n;!t8$duX6tSa9tKeu@>tnZ}E*1Hn4Bl4BNGV9giEO%Xv6Jp?U8+Fm zr)@DI0m)%VU&|=cCK``(oB)^L$5<$Ml5q)iQa)-nYz#jPZ2@j6G>nAOLX&vu1WDw- zLFm3X>pk8tc{9qW9EJ6+osEJrj`NKg#`>BHJL2`JHXEQf3mT;rdnbI~lu3c`#T}|8Z~9`WJt9C7!ZY0-{Yf3%jDqgkwVk4 z5}a|{yAyn(P%mn}lo}`_)#NxG=9y^T%Y)Co&P(H( z&3UiV!7e^_E@C+MPvw>^Xnn3<^@CH-SRFIMLad!~jYqW*g!x)}=N(N)fX}V#Y{MFg zBrJBlSYqo2$UD>{zfGU7o+o%uHv%S87n7=YAJ`9EzJsqhxx!mEpS2J(C%zK{;Cv&& zx5JCbs#$Fbx@zC@<%9t9jN8c^s_u-I31Vjg(M{p}@Ka?;?vahwRi z7q}xk+*N~y3$uZr;&%BIIZB9p2ovJ6B5j67ezo7F;_j;T`IR z1Wq^LqM!YaqX)S@_b(r&BW?~hx*-!fc1xc!67dS-8WCLVge%@B8P(x#ZpHd+*di@^*&xD85V(QU37EMZzI zfI=5155Qq^kQ0f_BBQxbn-j8qN40)FJ*jGJxeE=Y(9_|aoHh;q(44=#4%^sFOXSDx zi%hRoUAz_Z?x>Oe`!{8Ay^U;pD8#@Cfdo?7@TJIG`a{*-ou;pbFV?_$dH5gJ@HHe>PYF+q zJ|HtJT{Jur3qkCqi?$}t?_SRh;Ah@(V9|mo^X)T8-&eMD;u-QVd(qO*-b~T-jSr6? z@a4M2G8NfT+@!k?tSA2>;MuHb_@RDOHRX%NBAZ{S>~%U?iNzY(x@pNKnihMJ~%6wDgmePP1RjuWYk0*PtxGtIw z2|lE;+9bk+hYptU+Ci(#VL!6X%e$gTe}8L03}SN_TcPk(=!ir`59D{c}V~Ivw<5mNI7$aHvBl{Aris*;vzoghIqwL=B z*IA&w4gO%Ogxf~efZsqj6=I!2GIH8nol8h_=;Xgtq_3esdsTaH5QiY>%@*do&8ut3 z6J?o#!#5CVQerXqxCW1e{~^?se{(3rkrx?r8I2K`dS`?P@hKWy7^5m;w$ou%Wd@OB zcZ=Ay)EU(sl(-}-o28ewu4RT!$4NL+#&kb)&UKkg5Ok4U)7AvhNz9pvg3fbK0uB#E zQQApkc4qT_WmqHtHju#qA(zD#B6B!jElsFc{STBcTF8D6N}!@q3Ze#7Cd+cA`32NB z$@2d+CuDdL_Mb~yk7U5TuyuNvla)%Wb*9ekE>Q?MuJ7yT8V{_IAEG$73AA_WbBS}~ z#s88^Ch#7P#XHIMEc|WRs3jnLSQOf3g;)E}hv$my2qybjwK-othXs#=t|nY1IL@U^ z`uNGW(J$+dr-K>8j8)pWU?c=Jn}8?g`@H}9%4;^^I(h%?G@rx_Ug^b-xu-?RQoC4{ zQ5A(`^*BtM1tnPZ7Ku&_akF`2Jxc-B`N`N`E+X&s*w!6xANXM>cRJS}EG?ZMli0}= z0;Nitjz`n&;(g0PO(>><<+3=y57b#TDxW~S z16d^nu-@GbQ-1sH4Q7k*`uoe0P<}(+^YinP6u;bCN4X~n^4UE#*B~9(2?utQL_S3FNf_J=D;XH0CDzs*~?LBcjZWw$rIJxsR`mv z*>EP~5AnG!gK;1hZy+hJtbS3C%goxm<0IZl>%5kCOCZQZbXgy)T}>Rvm)D*OeLlj# z5E4n6Tv`oGuE*y2GxkWA=NM(^@^Ke_F`uyN_sR$|+8i!sp z!laMgDSQ4QoJFK4KV8Jr6hq3cGlRC;rs6I>qWSwC8 zQUDOia&XA!eVCQwox~u_u9&99wG#LxXK=)^geI z&Qc(=?Xuo`C-U@G2h$Xr9WAVh9z%g}qj* z9ofS~4vTs`?%7%Cqkiwt>A_eblv#$qChT4!4rG(%ohk-&e!yi$ixukM zf9i()4|If>E-^~J_7IVCu_boV$fbNw#@iJk+-L=buk9wEwwD)RxXx>R(Ufc3&-WB&tqT?+;RYmIEoz-Z#z}GXPE0bwR zT636y?BCIa04vZ?eCzZ8qX2-unfh_&r$Osyj>LfnodfL|Ewv(!)3Vi&Y=SpObFZm? zS0T3#D-#0`y68DaBI`;=4?4sWR4e1nmshWcnIXnX?D2iZ;l(g!H=g^ndyk89+E|R* z)-jm)>F-7DX5cRKgnIHn1gNuN-TCU$VXQm+e!V^aO$l}b1!52|NJci~9RYtsmp8>>k8X^wF8!+`4_yGnJfD&u z)9wdN;oy=uDkzM*y@|_(G8!7Y@7W|ws@DHQOxeal_D#OyUu(37g)K883kX>dtgitq zj5UzT`l*``OUtKzA_`mto0qIcF<8@0TK-NSN)(d9V@-5Ip6vi z0Vz(TWDEm;vjI*~Wxk9j?pHym*|CmZcD;l8)11TS}_3VQ`%GCLN!&m{lYTc=DUuJszLs&?}uW!dkr3`8;4}Z^p>Je~>x~D<^LD@7V z9^fSGDnsGuE zQpnIZI^0C|;al>FMrPn2Xv=SW%ZIqAj!?WhS`{u7P@J&e1!zhg((ydgl$& zPTa<(FO`%U+){HOQH`cy>46iB^ogB>KRB=YP%hn0mv_H;79u>K3y9fwMkGGp0kcbv zvW;N}=N_6h4X(;XP+N_)mEg)sUI3`8Sz50vX~@KUf93jloL3MptcVL>I4E?=c3%d6 zdU3E}?pQ;CFFJmQOis@d?EA->olei!hE#y6B)JMm7}huy zV_$?0zHUCav7)q&A80C>-v;A`#@6q^Ow73{1KP#j?+XDCOgQ|uJ!ab))TtYIS#(e% zmtnALH`!*Ai@W7Vzs{SF+4=f^QgY42yQ@=x4Hy6d=VDAClXdKS84d;qpQGclG~u^3 z>cL+0mqKRl$l<04tU#D_QL)R~=ne=8wxiTps4J2^yZyJ#d}ipxYH(QRaRJ^8gWMJyOcn6tb(rb8n8 z{z#rSk$X6Z{L7Y!tVhzMSVn1@(gwM}>>5}5XEaP>Ia}Hb@Zmul5M?qokE6vk;syl>u(p&`L(XeSc*;(_AU61!r8F}AkguK1IjV}wp>c@ATv73w!P+vY>$ zS8bN=pV_Q}l7xf3&uCtT=Iuduvhv~iLwmuVo3`S;_Z~{IQ3y{J%?@Ew>@(1{;%6Y! zPX^)w6K5?gcdL)p5m71wEOq-{4(fhrQn}hx0sbsjD_h|E~W_3ugXQ$7;SA zezOgV|MsfwM{X|i#Y2&U0F}oLBtFjHpBB93lEWQ1{VI~c{P2tI>yW0qEy_WT65*^1IxnP@9f-ZEL6V1t@ zZBbiN322`jy_y{FHSV8J@j$Ly%%PTS>k6Le?8(bF;Cxf&&=1w9XYK3Cr5``ZTz%#W z*4NSqlVCl^Iuo1^`lIpDCUCzRgBNF@HLKskL@QVHxv|13Nd^REU+;p>p1-Zn{;)_` zcjr16)6QhU%LkVZC%9c;EF1yJ_gQ2THc3w4UP8^#MLaqB{1SN7&0%Z{^I;oS+5`KB z3}jDjS|t$6dX6{rvj&rFA|1OiaOY~C4OsGntAi;P?MY?i$@{K&q=CZ<;cu|dWgaL23$D3iUp0?6}Wab}Kl4>fn?8O4^}icb!&RG6eJd`F?L zp4mG(!)~R z58s4iQZ+VXHB~^j;N~~;9@23q6{R@W6*TqGIGuk8BniXTL#MSjzR@Q1Kdbp8RHtBH zS6Y_j_BQBW-H6fS?xoC$z6%66U?J;_Ze&kfdA*)_Nut=hM7cPd2JRPOQMJ?r*;dN+ z62)whJC)$IPocR8>n)iW$ilW*oJ@P%kBq<4JIeM+1fCpL{WV``eKVBvt+WphTd^m0 z3jZI%iwbF9Us;CWa*}2ImU>?R;MHN&u|)zgc$jq89j->qvo=|=Ctdv5^h zk!fB11CuR@pv;nO93>baMMU5&dli{u$C;KJ$Ln)`Ok4ZPJDlt^?OrPX9mCv%uizt7 z{yG6|PEh{qwD_dk&^Y25zV%kr6=e=^wiYCS!prl_oqM>c9qIMOKc0G@!N3nNP3)7U zww(sLsywbY3>py=Tor_D38m~lCfF@gD=W%uH?uyfvugJJj`C_|x^$O*J^Y7Ik;t&0 znT6ZiqUB8hvF&;OMIC_JmJ2IqB44ii`~|VB^|Cz71PaDRmrq^l#NWR>gFB)cfA2%d={N3rS650=r>$jztdf2QODFvQ_r#O&345ovT|3`C1< zZRs{Kic8LyEpJzLig5JjvC$euI!LeMy)Hk@@8T+mk&0XQsboL7YW&?r01p^pL zIU*txZeOjVypX?S=v=A5TagWn3etLZ9Hq7n3!{q&SVUcq9OkU$J4&UOVT1n=PD>hz zNfBB-c){06kW(&KY!hgco^-y!F~CrKW!wHzV$2Y!0^a*e_QWp(sVW*ATK_uHP>JNg zAHOp(_1ef+ZmBld-!Ff0egUY%TI4|DFRl^{H zT9F}0ikPX^ss~F|XaI6KrrKGM6Yff6HP40NDJIfNCC$oe9pO!7m1sjEsAhUmS0M8fpWLxCzGhPV<$;UAcnF zRPQTjJ#pLgvPHi*5FAC6o*UnTmEZ=lpe~N#@4z4$!B}56Lpyt~cM3Yp<>Oa{Nm3q( zhB|eV%l=0MM>u`C_!HUTJpuM(4W!`!*YCMfFgFPjjxm{8;m+$N3pbMY^Ml?^`_3D- z>tS&3I$g>qHBQHAaL*qT?b(-w{4t96Gwufxp?96#^&yqBlZTYqc+b zUXH=^olusXg6@hQGzouNLQLsMjBl6 z{m++Z=xxSrfN;mur|12SSp6w3Cuec4ENn`2A-<1qCxRv*!TfVt2?x95S ztz&DeaityIgg)xtvq5ST1xa!2r<|=f;kpIsU9sPIFY1|4fFNFmj7@XRi)AesmRMs3Rw++N3smgN&9*a{hr{A)1ph^t~lhydVbVS+D#j2b({KvUtpEpe0Ca(1(&b&!m-2`>ZN@rhOVtwrSO z`k`c3l(~w`q^fHC;7tddMnhWg7M6mpCvzbz4F5Gw9QfHclaHt4_sy^-~t@Xt%94`$_g@{JMYhbAF|T@ z-a6|$LJ-VE9BjHKBYfbX=dJJD_zDBltNr@KXpsbYDQdLR!&*&6I$Z6jEcmmOX{XS^ zF3s)E++5-mLjAYp#l#Fi5uESM7d068+29dSorxoOpnif|_Az|420#t@;6^}kh)e%T zkd+!Nh^G6&8a7wM@(aIX&8H~Bcm1HU*K_`BVGfP!c-F1+uA$O-6@Ux%Rc>)bsbO+l zyT$iLQ-Zyrv)XAibR;>qqtY|^$DV|Qv`ihVO7xr&HRn%$zn!ni(gg@zx}(iqv4yW) zK(>ko)u>Db%PE;D2}xHj{aBmc`KF5I<9{JIc{|cDN?OfkEHPdI@m(_3vw&~rVja40S*rhibUGXZ*O-sx?xLS?Up8x6{~`DmQc2ztj&_H` zm1H*ZU?zQHZQbE1Fq7U9lRl;6nvIfM-She<1_I~~tQo`VtRm5`$DHDqrH2^CqIqBJokmJh;?{GePpkz++*PS)YM{m5 z>0`X)>X1kW68ef7Bv|?TYt2H5UI9$Foy)4TA2Xq?yD!H*&Oq7FU}3gfm)|M?-tb__ zAxA6M#ZeIaw6_5eCow}^hf>04BL$@nW$*PK2jeYGL{IallVUAR|-BlUO-Ho#=UGufTT_rqvX^2apgR zP4F^T`;GWF$$n5q1kLckdKst6A|__0LQ=_oMWn~Q;SDE7zqX0#`^dY0&M(EfC z;Bf6MQ3OQi0C77SXB!3#MR(cY~*etUJ zZ@D@=tE06DVB}Vwm&lVTv3~N7UtrEaffQ@HSy(Jg{BKN5(35)lwH2BT)=oYtYhaY` zihW&bFj^BcIsx!q51?Q~EiLH?6!raiIjf)p`pK80T@@)42fUJJuw2T^^vX8#u-+R< zZk~h7eg^|}BP9Rw!Cj=?Ml#>C#|iyInDDa2S0k%K%Bb-EiI_P64y>?(;1!T#s;;p= z*;~xNYDtXiFyzOPmYe*V9%T)$PTF+oc*vb7dUQH}3}@5}&3qrsSM`pIz6TG$LEn?D zd!Gay-JFmO2Ay)AgZC#HXNKD58?neXgK}9;3d-Wh*kSI%m)-yOs|ukR^YFbt>OU%xK+FhLjX`~B(!CVL#& z8dO+YLA!jG8q2Yg9;OmLK~}q2z-ViEyYveBz@jLcnCCElWd9EV%`2%+ z=(PB33eI2r@t|1me3sweR0k5qB;6oK{{E0V{0{+jd||4oXS9-E+YD^JF{a_Ko0K_+ z@i0=pZL2sHX-34^TvPuxzUPe+$Buk@i%io&KZ_fIdT0zuP{fBbRcPHG-82V~G5woT zKWm(D{;;{`-u%^K6+tqYUa0CBlllh(%{`N8xL)fas+c)H9qk`6@DZ6bXqLl1CsTqc zy#ErMUtf!gSuGsE7w+dYf>=Kf`w?HfT*DQ%FOU^2ubs2QZz1``f0@$t`zDnkX-tYC z)5_Obw!BZ563bV(0q%v~6YZXh8yX!vZ-s0%pU&B zO^F`FaGhr$@Q`oW0v?11(S8KeY&aOkgBqRQ58(z-L$b60<4gJ7^|k%cG1D^U%kzqC z>gN@7&|CA8cbkDPIR#tPsrxegaUrJsDs9t*ga^yoOAQG!0p5kJu~=P`ecny2Ue$+m z*%VUPlhrEEc56N6bW1vu+p%gG)J^a1BHji4eKxXHzHCtBb_O+Un!7XRDN(?OzwC-T zX(Yn4HB>!R%WZgkA2Yq;qE!DQX~-D6oyC+N5n?-u+?AG%d>qQ#pAVdrT#6IE;LkX2 z<)>9LvaFEFPzG^bsc3MNT%4=;?cpj-%XXB=&5%&@20%l)Ag8`D?1I608tZZ|+a%O~ zU-}~3bSV0|RN|wWsFL@l1w1GRN4*V=Eq5}@DING;N4f6hI1?|oDW>SF4zm31UZyeE zE_Uz8+_|7{(Eh~!fz4s#6kN*HkWnIKWx2P>Z0m<5>V6k_>~ zQNO|jO<=fn$poJ+loa)CPc**l!B=}zcloe991<Ba)*4VQzj?BW9%46Z&zesx0rPI2 zoledIXQE=7I>-81O8{Z&s&$a5TO(TVaRl0(gU%{^!JL(G*ICqr0r)nX+v~*+o+sRf zer*2r@pkcsJPK1z?CDP5at9zrLdfk(zR9#dbu(oN^Yey^+62jP_nSiD&gw7iBQZb0 z;kWqP7SiYVbjaxP#VbNtZU`2+VnxkY63do(Q+#ylv2%X;4UbI_QY`1$2!O&# z-2edHkIcc9ptrHe>cbkDZf;+Mekoqr8g=uG?K5wkEh!@9K0<&Bg7pC(HUpG zHCj#ysBSKOOUUvis?}FZh*MFL)^L5MJQUH5)`NEx0xSe!yf zs8hZi2WA|}h&~}dJpWSQ?q6&Y9sBOubhVc^*TfXG<>aJK4B>U`sIP?gk!#~G^XBBj zGkwcEFU!e)2w}GJk)v-mrg9w1axa2KlKvs6?%EDC^p`}n^TAH@_eNFj64=n~EIkC` z+@WU$71lo?Qvr!F>Bl-xR=>mQnFdSJd@#97cbCzZ0blxd#e&!7ZL-)&m7j#IL@zYX zWU0MtMdq6%-08Hf-tG_1w?>Bbb9GBdaZcmr`GRvS!gdsbc>d_#{}?VYOQ&A1*)x&O zlJu=J5_b47{ zebq`9w&oR$DhUoF$&w-9zbFtS@uN{P-aG@Hfze95uH<_eTrA`u8DU4PW16F{_R-5b z%cOX6{UU*#jSUkv8C@9ftL^ow3Khttzx$I1<718N7W={;Z1xj^sCGjuULKEZo^3V^ zbV3Fjntn|rHnRe^57*W&gH5(a8Z(phRnkOl4rK|TZSJr3JpcQ9D^!SO675N%cV~Mh zatRhm#9;Sj^aS&`i#Bl07?UmB2l8mD7ZPfTY28O;9Ly7AFC*6_?pdl8rzMtS26l2w zqxJZ7^vtQ}Y%JRlUwYFx(BRmsAcY^jHBMMWzu0Kr^2AYsnvUPQF}xhA-hc~Ux^?CY z37CnB=3>57mldhIPvc^uv-$CI(=b45q|jiMS#yC|?GZTy3Wp46_|^=j*#PMz$>*D> zJPJs=wu{H*gkl>?vV;w3KZ*D++;VT)IXhr+Q;rdVlNN3*+<;j9LXn1*Cb(qA%j>6$ zLg%cK#n3y`bkn6rgMv5I6eq5$mzX@*JiK^_l@SCLq0C4 zl3S(1++{W~UcHD@Ib5e4j@y=5?qxbU>uZP49iqhnIk#e}Lb1MVjJ>>{y5!m$Q+S8ROMBjKilW4%39b6N z{@{i*>ik{Fn62k}?%R?rdd(YOn5L`$)zgn7sLAr)B1U!BWi+I}=`7Mr47SatF8xmCN)4~ZQoZ&`>4Kc#`?9ljXYF9F zPZ*s&M6x&;icbq-u0{Fe{wbcfN6$g=_}%5o%6AXvRDc(q!x)8bMJd0J69IzXoLwVc zht7cSL}gI@r&_;*ISu)bIBQYc_EN8up)$BlQ!jlcGAl(G)K^BH_P%D>USuRo4jMNA z9w{0rB9{D=a0rm#A;__-!*?fZ+ihnzJK(C)@$yV#%?qp()W+VqeDE*>$1pZpPI->2X%C)L0Sfvx_QJ%e2Y;7?{8*D6pJ?%khGMcpcjkIQx+0w8t=2$<#;G zzA*?}?>Myn9Vv{8%?_I*P!Ah3uPaTB)#XDQ)<834^e_V`Cs%(AJI4ZRHsaJJb0m9} z#~uf#kYyZC|E)bZExOJQX)NXNUm`^j6~c8`R|IWZV92nguyC5ms(@pY8weZ)zI?O| zp=Sw{lain%(y~Q<%Ow~4$lp+kdmMjQxHD2)lFF!b$^BF=)r*C&IA7@)Hb?!I6sce` z&mwDthGUU=VdyO#SJ9Ta0QxfJ{-p{TH16d#c?OJ9ttARwU>vN;N$X%8SRjxcVofjsv5|CB;p_>C%Cf{@o zE%kOs0JSUc?Hum?x@#BDuYhJ%8|hD&*6>6{KgKuu;MLJw8G$}SrY+bgn{DeP;StCs z)f+c3q*_rg+vQW7#{BL9*7|E?v`@C#{fQz9!u+>kJoqvcuK8 z{Gu_6Jo=@Fhb}4vNcs)x|2fCe<{!dmTnb9Cbo+wFbWGb3fOo7R9|LDi-YN6q<*Jxc z9{1BVExmBX?H91xGN;zi%sg>ySfWy#n=fETgP8PVJuf{?4Y!YT{?as#EWu;0O})r? zY*^N!33`~@MH>oI-uzY?U@+kOWR2ncSc)z#&H zJ+X-YOWDDI+bEZ(X-~8lYiMW#27btWd;+>vo>G_#B$en}#PjTTCRQTI`rngGz5~|k zp8o-w)@+!)F252pAB(Nc@A97~F2H#ML4LFUAq4tLeC?N2WvTvQHqiQ4Q8)$wk1>VB zv$Q4s7x>Nh)?PN4DweStdajzzc+^j%8&E~z&fKL@N?rq+TgTLJjJnZChhAKYvSj%>| zFHDj#)e-RzOnJFhG)*$+rlJw6!K4R*FNOo`pQO9W;c)eD^d$!{$+ueGUWxN^Itlt+ z6eRwM#^JdUVj<9bg}}I)fB<{mky@HJ{yURVop5a_)xi)nH3}VW1QA%>Jzrn#2z}1# zL3AujTg3?ne?WDyEdy?bNUQcpt3d8V_C;X31^ngp=OoLiYLsh3x;UD181>!2!<@SD zg3@XZ8{F|F60dV0>rVIHb4#s7H99L<`GlTbZnGqZwhLPx44+)>N&sIj-aRlecsptA z@hq{NJrSYB++RJG^n7FUyn?`Xv|=2t_s2UhdPA0ywk8-;opx-eTyHL0+XpK5xdr4| zPi(F;q4WDC1&K7WRP6+jUJ3uw8x3pD&b2O*ac6m!T%LW9T#IVrRu5a_G^!~WcTjv1 zOmoO(UbzFIEbmP{AirEkez@lz$SiJaXwPP<5wz;P7|wMUZTcp-`5!!@O|6MAXYWzC zHDyeObQUXc@*od{xrN-7 zUMWLxWkX;=ll^Bk3>|vD5P}--sjBGr-Zpmd*1}%8^Sm5zn|6@%<@MBqVF*t|s zzXritb`wzy+kQS(7`$awrRQyuTpBz7sh5xiPiXadqc|)_(sWQ`7WGf_TU(JbZ+rs| zdN_zVbzm_DS!w`0hne~+9ip~b#k+%}GNHZo5r%ayq&A2ivpI){Vyh-H#QE&7^kZHf3T&C%=PX`y!MPIN+S=#A3N=wTt<6fK{7&S5-pF z7WA7{OK#ZzQH!CFFRX!=e*~lajy{PF{O|cOH0nD2DA}cBgaY7_>!pzP{-jn51_KG#L!N+h+ZOI-ht2qWkXZ^Yw9X5c+v|gp+o9 zv`F`tpP4S?y1H?CGr}I#aEmIxdlQ|7uA~H3Sda>qP z=@*U&hcVo)@eVhQUUKsWrJ21A_KZC7J|~|u?^x1WYQ5@|EYu|=Y~LIGehGQAVG>t~^m740Bskqv!CRXzNnwgLYfim! ztlh#&rj@CqBF87WMU@@zsY$4|&lPgsY)SF?@Be1w>;-`!*_jl93<}<1KG~}K+XeG$ zB?ssRwBmMp%k1xbB$xRire1OY@{y9a*|kf6#t=OLMxr@C7!LSKWe*=EKIPA?u$wdx z4g>*x%2&&sQe+{``J>Loh9rKJ(hGChDjDJYu2-FymFOe5@L}0;_M-fAcj3an>TdY5 z3CXZxTA|JUbyX$dmIJRW>#SM0v}Mlc%fxyKWOYUDgP?oKk}Pt^(wCOzi&q_Dy5_d!_JA=!`T`t+7pQgOhaKTn4-}`eC4vnwFUe zepEk&F7mu1RR-x=V>2tDx>VyxQmp0t9U^|OcHf+sxuvA>)IxW@mc7}YNBvQgjA`vg*NYT}}+wBqF(%`r<9 zFbd!j+h!tE+mq?CIUIOl%Y5uyH!%S>uwAV~b5y8!nLR_ zJE1dj?D~4V=P7q>^E*juK3R~-aH+ZTXsVI0;6p3j&$WMO=}B;PWtm_y_+4`9D?O*_ zPhk6wNn@B@36WI&Ujg@~3P>>dG~aBV@$B}NtDiiZ-V9yzb8^)0@7oRhXHAaTd~5>U z!EZ{tOZra0$s#?5)`tpDO3%oS*@?dE!m@YcI-^lUNO-uRzM5#f@(|yXG;)`v+VZXg zH~*V^ow30M*hRU3iO|Br2iI?Wm@~KIb2Xw+rXk#U&MKjya2iK~PwiQ6wpW<5ethbG zFhTenBQ{f(rcxBn4m>yw_RLnUU-RIZ^hZ?qAmlu1-rI3izLf#H5rM(#fJ(a#=%(<( zsqwr^(TKj|%oEd)b@r3Py&VDMLZ1v zvDOOVl{p!BsRv%?sexxzFu4KT^ozWwkI!K@N9*X5)jDA(i!d|a1U_j26QPM7;3`b&zapRXFJ z&H}rq3^=N=hc=?wp`LrS(2~F>VoM;|;q(mJnnFI8 z>M!{^gSC?721>5gZCXzd+kQoa0FP3r_khv9q1mh~`Ts`xx@M+Z@rC*S@BSg|*}+Lt zF^^K44sa_&MjS-AM<*f@67qj0ornks2#5%{)EpA(W^Ql;L!7$b)8`vh7wqEyem zjQUko?ip|h?E8oCdmQ_<5$^e1_X8+b^besYNDV!ow?ipXU#0(s<9)cuTY9y+QY-VJ z`e5oK8_DM~FS_FxR_mh1JVmxTT;9hS^e}ouH_99R^(GVn>qm#%#^3EC$&f^2T)c*s z4sk6|J7?WrIz}rI2b^gBs*Y>?td$YXFM;xt%l z0E9q$zdghc(J*iJ24?5kQ}!U64eV#`lKJFSPueP>;CEup`g_M0@vC*R%ZBf?LfZVr zVUhNRw2B(>UHVI{u8VE={{Z27+l_t8i2nd~l)&nb!VAEx7Vh~=wN%MfebWA#Gx|_lsQYB<54Pp(cvCd= zj6E~??vF?~A)ndj)(R;qHA|`O36?gSIj_v+f!jwPx3|-LT)MJ23-;U;n#qXgbH?DB z_Szm@FJ3Myk`#FvdJAAM6?*5MVN@o<4=eAuZ(O0MGLIjK(_3Ruk0NsiuYSbZEN5$# z@BaY7f(bVpWViEKe zD_%|}lleU#^>Ec=vX&p>rqI~87Vw7dCdy6a5DYOn{LD+6tI0N4Zyd$2y8Jm_W|ENY zDbN{Z$nQL;eb=PkF8i&eVBP-!z5afrs*BUHDl(tIN-kc2e}W;0C*Oaldht3=^S%~< zw$=5CMEAa+bn_fGZ27nkiFLIXy?@u*KLLe{u(fuWz&B&O2ec#yS!veXrC8qr&!(nj zeV@;-ydN4jv(Bwd$1Q=T)f=y#LyraMciMZwSc8= zVcB(EcX)fcLE5nIDEu;waJDjre+Q%ftxYX9v>x)>4(X$yc8i-lO^d|BkoyB{M5@|8 ztCi%tI&ruFvlv>iuB#JWC}Zb}Rb8&7Yc!Bpe9ZJ)+ID*WAv_!R5kc-Ou6g^dmFT zqjmoP54^KkcgPp-Hx9RmViZ`J&W~!G@~B~* zycV>)gI^GYN{0(Km%nMe38^cx@oiKpVxl&1^1b$BjjOwh7xf#;y&a$23TEK;clRRW z?PryLXX?Z^?nCt{{OolIFh`bGv?yP7KbTb9UgPy20~h=~TYGtzQVLd_HRZoTQXe>2 z^H?Vh1GZilQBg_sGl5{@CH^O0SXL0(6~>IO3!kwDG982=#)D4pQp>D8 z&TZkXLU>02@bt@c_Fy$<6MU|@<>RC(LW-{K+Hs2Zg-R?IiwEE770WlAI{o^?o-MJv z+g)&Ul@e`%)zFPZ1!ECGc z3LtXZU4Nb8^@kKK=)eRDJ4UR-`I#lrEGp$)0_Dppi{}B}f%*+gRP+9K8&R(COUFI) z8ubtqo_3u5IzK$~EGtf7bktS<05w>8VgPnG(^$^<#2?wle{-k)uB|t?sKdNGlK?Zq zR?I>%Tq_x~zh6KO0k?YG;4+`4r&hp1aunuQVx^d%kDtjgz&KWslS7I z7b+Keb_#-%FIo@G=alu>L1Jalen(bO5PXjb;KKSs4xj>edqYEpyd?q9`_vi515`$- z^S^+bSFikMY$E@$wUIl&c=x!S?AKC0B zK}ekB!|N0+VZE?xHvZ;z@`Bc}T;(>ls3~+G-9-d6HqkP?1-BYJN~z$Y)&rI*?d@|Y z#xY=Bo$nKbV-K=_W2gS=OfOIVjM_!4n5M1nTk&^;Ts%w*E}nXkH+>a^c2=(LdbDNW z^1&ww+ZMv!T#RyTMK%Or@+|xh*qO!M`GGc`Kxn#`P87q7ZX%SdS6Y>AN2mRkJ|-X0 zgw=rqznV}gP>gLWHtT|FSVrS?TvEA`(g zni8pd7cdp&gw<=7zKCAYa1Zs3cE1@A$gYL?!tyD+X}>cmg7#HKveQMi7%Ls2wh==| zKKMKMf;|fRpJ`{J6P+}9&!EE^(bE~1(5R=sifuB8{{Ro^a0YQjjal~@lFLW0N7T$0 zvAULao8-&#v8rJQ7Cy!#(^fA+`fDDu4@+*&j4Rhh;ozHfwRK)tZdXaGa`_L$I;HUm z*M{Y7?Dm_d&{^61t|c9ih457V2T%R#*~@&w)Y3)G4bJJYnJ)N)Xu(F=a-5jpU9G2@ z{Dj06=+uj~BOxyA+}A{Lp>_P_UMQ4GVf*^TD!Y2{KZsNKAHMTS8oV;b2RXbo4$9d5 z&%7}lt-|Yt*?uenBYzR$6rp7sCzaTUxJt3 zS-OQ=-c{_siB_%Caq8OOw4DnwFnxZ|006;?o9-D6>qn~rJl*kS`%ATFU_K;&5y}U# zG*dpv^^qR2ggsoolO7lz0CUIIx8`+*0tY_wkh)*&hk`KGl)!q_$@i71N$kCT-$U;= zd7r@P{{XiOe=$F2tSV84%Pf~-n$~jXpLSp2S35LzZx5`)Im@C~!_(=2$xF{&M@a>G z8)0Q_l&TVn@lg6O&hxJzo{Uy@#pwd9`G)O#GvExylB=4J%Q1lUv;0D<$a5U;gRfd4 zE)Rq4E#BmB%&jTZQRX=!V-$M0CKYD?0Au_U7n=EN++&PX)N?l%`h^8ABmB91N-kD8&#zj&pz3=4qB~k{S?%%uVFA+^YV(=dP)URS;ioROq)^i)u z3?DDVO&F8ApM`tES6c_l43A^iW4^1_2UkqihYP;Z5^;s8p+7M}%L3JY$E>&;Ko^$+ z)@oisL3do@7)qO9^;Z$Yx(`#A)tfxe=yd-8)lE(tenft~Avj1%j2`UW`NSKr^xQ4# z`UI+72Ajrd)cJ&{+d-aJ$F#|e37c|WrDerijt9P9Q!TWV91U)w| zdwD4I!E*1M7uEe9^NuLo(KG0VA7*C4g?YYTCQ(qYt>5d@>D&w4L5dP4=`jmNwtLK6 z1^IcyrZgC2B8R&NuQ0C!CV|FjipHObbVP@Pv6+rav|#K`LmTaO9D)h;~E{w35kHX3OBtZjmah7ZifKk!0dfb#no z`DO2C69v#laDWG++g;@lbLw-v4w$~ ztKG^#?jGOmp$Q@#08QgJ%GSG~p#%hQ&h zqXj{leaGrjZVmRgZvBQO#yozIW3%-?_mj zqas}(fP^iAjvRBUgE`gi6RE}ahNUjW&H$pGU%k{J8Wqpb8dAOWOD|1iA5vVkS#9qZ zGJ|S)FmnF@_LB<%e=5;3hz-D;=t7ozF_C_k6`<;HoXef?KaB1GIt+%0DwhC-?>Fc_(wk|?kH8I^_L0u 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 literal 0 HcmV?d00001 diff --git a/content/post/first-post/index.smd b/content/post/first-post/index.smd new file mode 100644 index 0000000..4d6c70b --- /dev/null +++ b/content/post/first-post/index.smd @@ -0,0 +1,110 @@ +--- +.title = "First Post: What's A Zine?", +.date = @date("1990-01-01T00:00:00"), +.author = "ZigCC", +.layout = "post.shtml", +.draft = false, +--- + +This is a sample first post for your blog. + +This post is defined in `content/blog/first-post/` and contains the following +files: + +- `index.smd` +- `fanzine.jpg` + +Another interesting thing about this post is that it uses the `layouts/post.shtml` +template which adds at the bottom page navigation within the blog section. + +Enough about Zine, let's talk about zines. + +## Fanzines + +A zine (short for *fanzine*, from "fan" + "magazine") is a non-professional +publication created by people that want to express themselves in paper form, +usually in relation to a cultural phenomenon of some kind. + +[An example of zine from 1976.](<$image.asset("fanzine.jpg").alt("A photo of a black and white flyer. The main title reads 'A night of pure energy' and the rest of the page contains a mix of pictures of guitarists interleaved with other text snippets, some seemingly taken from a professional publication of some kind, and some handwritten to announce concert night dates, presumably for the musicians in the picture.")>) + +## Zines in the digital era + +In the digital age, some of the cultural impetus behind zines got redirected to +personal blogs and similar digital, non-professional publications. The 90's and +early 00's are famous for their whacky websites full of clip art, wordart text +and "under construction" animated gifs. + +This initial organic exploration didn't last for too long as the rise of social +media diverted a lot of self-expression energy towards walled gardens, a change +that was also fueled by the much tighter and intense feedback loop that those +platform enable. + +In the 90's the stongest dopamine hit you could get was adding a visit counter +to your altavista website and watch it go up, slowly, mostly because of your own +accesses. In modern times views are almost meaningless and interactions such as +*likes*, *retweets* and *comments* provide much stronger positive feedback. + +An unfortunate side effect of this new cultural wave centered around social media +is that not only you end up gifting your content to platform owners, but you also +participate in a system where the *language* of the social media site shapes your +thoughts and experience in specific, and often user-hostile, ways. + +Art, just like liquids, takes the shape of the container you put it in. A mobile +game that lives off of in app purchases will never be truly great because of the +tension between making the game entertaining enough to keep players engaged, and +the need to make it boring enough so that they will want to buy upgrades to make +the game more fun. + +Similarly, self-expression on Twitter is encouraged to take the shape of short, +hyperbolic hot takes that forgo nuance in order to create catchy quips that can +be used for hasty decision making. + +Likewise, [corpowave]($text.attrs('wave')) never goes out of style on LinkedIn. +Spend enough time in there and you will become a character from Severance. + +## We're not in 1990 anymore + +Despite all the issues with social media, there is no point in thinking of the +90's as a better time. It was not. And despite the winks at the past, Zine is +not a tool for indulging in nostalgia. + +**The goal is to make art**: the act of inducing a change in others through +our self-expression. + +You could argue that the 90's excelled at self-expression, but in doing so you +would also have to accept that social media is infinitely more effective at +inducing change in others (albeit at the expense of freedom of expression). + +Once you realize that, the path forward is clear: + +1. Own your content. +2. Create new social systems that optimize for creating art over engagement. + +Owning your content means that you will be unaffected by enshittification of +platforms that would otherwise keep your data hostage. It also is the single +most effective thing you can do as an individual to take power away from +platforms, all while protecting your own immediate interests. + +Creation of new social systems is a *slightly more hairy* problem than self +hosting a static website, but it's something that can be done. Over the years +we've had plenty of social outlets that have allowed people to socialize through +their homemade games, music, drawings, fanfics, etc; and chances are that we +have yet many more of these outlets ahead of us to create. + +Zine gives you a small puzzle piece to help you inch closer toward a better +future, partially by providing you with a new iteration over tried and true +patterns (e.g. by facilitating content creation by separating content from +layouting concerns as much as possible), and also by being a bit experimental +with the concept of a devlog, something that you wouldn't normally expect to +find on a static website. + +Lastly, Zine makes sure your content (both blog and devlog, but also any +other content format you might come up with yourself) is available via RSS +syndication. RSS feeds are far from a winning technology in the fight against +the ebb and *enshitty*flow of social media, but they are another small puzzle +piece that costs nothing to maintain and that might turn out to be critical once +enough other preconditions are met. + +With that in mind, **go make art with your words**. + +-- Loris diff --git a/content/post/index.smd b/content/post/index.smd new file mode 100644 index 0000000..4925d42 --- /dev/null +++ b/content/post/index.smd @@ -0,0 +1,40 @@ +--- +.title = "Blog", +.date = @date("1990-01-01T00:00:00"), +.author = "ZigCC", +.layout = "post.shtml", +.alternatives = [{ + .name = "rss", + .layout = "post.xml", + .output = "index.xml", +}], +.draft = false, +--- + +This page defines the blog section and lists all posts in it. + +A "site section" in Zine is a group of pages that form a logical subtree of the +website. It's related to directory structure, but it's not an entirely 1:1 mapping. + +What defines a site section in Zine is the presence of `index.smd` files. You +can learn more [in the official Zine docs](https://zine-ssg.io/docs/). + +Take also a look at `layouts/blog.shtml` to get an idea of how to render a page +list in a SuperHTML template. + +The blog section also has an [RSS feed]($link.alternative('rss')). + +In Zine, RSS feeds are considered "alternative" versions of an existing page. In +concrete defines the blog section and that lists all pages in it, is rendered in +two versions: HTML for human readers, and XML for RSS readers. + +This is the SuperMD frontmatter code that defines the RSS feed: + +```ziggy +.alternatives = [{ + .name = "rss", + .layout = "rss.xml", + .output = "index.xml", +}], +``` +[(btw syntax highlighting is done statically in Zine, no need for javascript libraries, unless you want to)]($text.attrs('small')) diff --git a/content/post/news/2023-12-11-first-meetup.org b/content/post/news/2023-12-11-first-meetup.org deleted file mode 100644 index 73aa694..0000000 --- a/content/post/news/2023-12-11-first-meetup.org +++ /dev/null @@ -1,40 +0,0 @@ -#+TITLE: Zig 语言中文社区第一次线上会议 -#+DATE: 2023-12-11T08:25:00+0800 -#+LASTMOD: 2024-05-11T10:35:22+0800 -#+TAGS[]: community - -2023 年 12 月 9 日,Zig 中文社区第一次线上会议隆重召开。共有 8 位 Zig 爱好者参加,分布在北上杭成、美国等不同地方。 - -{{< figure src="/images/first-online-meeting.webp" caption="会议参会人员">}} - -和当年的从仙童半导体出逃的人数一样,不多不少。😄 -{{< figure src="/images/fair-children.webp" caption="硅谷八叛徒">}} -会议伊始,成员首先进行了个人简介,便于后续开展相应工作。随后,社区成员围绕 Zig 语言的普及进行了交流讨论。 - -在交流讨论环节,大家就 Zig 语言的普及面临的挑战和机遇进行了深入的探讨。其中,大家认为 Zig 语言的普及面临的主要挑战包括: - -- Zig 语言是一个新兴的语言,知名度还不够高。 -- Zig 语言的生态还不够完善,缺乏成熟的库和工具。 - -与此同时,大家也认为 Zig 语言的普及也具有一定的机遇,包括: - -- Zig 语言具有很强的性能、安全性和易用性,具有一定的竞争力。 -- Zig 语言的设计理念与 C 语言类似,对于 C 语言开发者来说具有较高的学习成本。 - -因此,第一阶段,我们打算推出一系列教程来帮助大家学习 Zig,目前主要有以下几个: - -| 项目 | 参与人员 | 目标 | 仓库 | -|----------------+---------------+----------------------------------------------+------------------------------| -| Zig 入门教程 | 金中甲 | 让没有编程背景的人可以有体系的学习 Zig | [[https://github.com/learnzig/learnzig][learnzig/learnzig]] | -| Zig 教学视频 | Onion、Lambert | 同上,素材取自 [[https://zigcc.github.io/learning-zig/][Learning Zig 中文翻译]] | | -| Zig cookbook | 夜白、冯文轩 | 演示如何用 Zig 做某个功能 | [[https://github.com/zigcc/zig-cookbook][zigcc/zig-cookbook]] | -| Zig 构建系统教程 | 贺鹏、陈瑞 | 体验 Zig 编译系统的能力与优势、与其他构建系统的对比 | [[https://zigcc.github.io/post/][zigcc 网站系列文章]] | -| Zig 写 OS 教程 | 柠檬、西瓜 | 体现 Zig low level 的优势 | [[https://github.com/zigcc/how-to-write-os-in-zig][zigcc/how-to-write-os-in-zig]] | -| Zig 惯用法 | 全体 | 收集 Zig 编程技巧 | [[https://github.com/zigcc/zig-idioms][zigcc/zig-idioms]] | - - -我们希望通过这些努力,提高 Zig 语言的知名度,完善 Zig 语言的生态,促进 Zig 语言的交流和学习。 -* 结论 -Zig 中文社区第一次线上会议的召开,标志着 Zig 社区正式启航。如果读者对共建社区感兴趣,欢迎与我们联系。 - -- 邮箱:zig@liujiacai.net diff --git a/content/post/news/2023-12-27-second-meetup.org b/content/post/news/2023-12-27-second-meetup.org deleted file mode 100644 index b693050..0000000 --- a/content/post/news/2023-12-27-second-meetup.org +++ /dev/null @@ -1,45 +0,0 @@ -#+TITLE: ZigCC 第二次线上会议 -#+DATE: 2023-12-27T08:39:51+0800 -#+LASTMOD: 2024-08-18T12:02:01+0800 -#+TAGS[]: community - -2023-12-23,ZigCC 社区开始了第二次线上会议,共有 5 名 Zig 爱好者参加,分别是: -- [[https://github.com/jiacai2050/][西瓜]] -- [[https://github.com/xnhp0320][贺鹏]] -- [[https://github.com/labspc][Lambert]] -- [[https://github.com/fwx5618177][冯文轩]] -- [[https://github.com/1000copy][Reco]] - -这次会议主要是同步了之前会议落实的 action,主要是同步了不同项目的进展,由于临近年底,大家进度都不算太大,但还是有所进展,算是开了个好头😄 - -* 项目进展 -** [[https://github.com/zigcc/zig-os][Zig-OS]] -- 主要参与人员:西瓜 -- 进展:粗略看完 rust 版本的教程;完成 freestanding 二进制,现在卡在了 bootloader 阶段 -** [[https://github.com/learnzig/learnzig][Learn zig]] -- 主要参与人员:金中甲 -- zig的进阶特性,诸如构建系统、包管理、与C交互均已完成,目前教程内容已基本覆盖日常使用 -- 增加了评论区的功能 -- 待完成:反射(编译期反射和运行时反射)、内建函数说明(包含使用例子)、未定义行为、wasm、原子操作这些边缘部分 -** Zig 教学视频 -- 主要参与人员:Lambert -- https://github.com/labspc/learn-zig-in-the-style-of-c -- 暂无明显进展 -** [[https://github.com/zigcc/zig-cookbook][Zig cookbook]] -- 主要参与人员:夜白、西瓜 -- 已经完成大部分内容 👍 -** Zig 构建系统教程 -- 主要参与人员:Reco -- 目前主要是对 [[https://zig.news/xq/zig-build-explained-part-3-1ima][zig build explained]] 系列文章翻译 - -* 新人介绍 -在第一次会议后,有一些朋友想加入 ZigCC 社区,经过简单筛选,新增一名成员:Reco,下面是他的一些履历: -- 南美 Optimes co.,limited 联合创始人、CTO -- 任我行软件股份有限公司 集团CTO - -其他技术兴趣经历 -1. 图灵出版社区签约作者。4本电子系列书:《Vue.js小书》《Git小书》《HTTP小书》《Swift iOS开发小书》 -2. 微软 DotNet 技术俱乐部 2007-2010年成都地区主席 -3. https://github.com/1000copy - -非常欢迎 Reco 的加入!也希望更多对 Zig 感兴趣的朋友加入我们,普及 Zig 在中文社区内的使用。联系邮箱:zig@liujiacai.net diff --git a/content/post/news/2024-01-14-third-meetup.org b/content/post/news/2024-01-14-third-meetup.org deleted file mode 100644 index a9fe9f4..0000000 --- a/content/post/news/2024-01-14-third-meetup.org +++ /dev/null @@ -1,36 +0,0 @@ -#+TITLE: ZigCC 第三次线上会议 -#+DATE: 2024-01-14T09:20:41+0800 -#+LASTMOD: 2024-08-18T12:01:56+0800 -#+AUTHOR: 西瓜 -#+TAGS[]: community - -在 2024-01-13 晚,ZigCC 社区举行了第三次线上会议,参会人员: -- [[https://github.com/jiacai2050/][西瓜]] -- [[https://github.com/labspc][Lambert]] -- [[https://github.com/jinzhongjia][金中甲]] -- [[https://github.com/byte911][夜白]] - -会议主要讨论了下面两个议题: -- 公众号运营 -- 如何与其他社区互动 - -* 公众号运营 -这是最近群里聊到的问题,由于 Zig 语言本身属于较新的技术,因此社区内资料比较少,这导致很多感兴趣的人没有一个好的学习途径。 - -但对中文环境来说,我们其实之前已经积攒了一些素材,是完全可以通过公众号的形式进行传播的,主要来源: -- [[https://zigcc.github.io/learning-zig/][Learning Zig 中文翻译]] -- [[https://zigcc.github.io/zig-course/][Zig 语言圣经]] -- [[https://zigcc.github.io/zig-cookbook/][Zig Cookbook]] - -目前可以按照 Rust 日报的方式,每日截取其中的片段进行发送,方便读者在闲暇浏览阅读;另一方面,公众号也会介绍 [[https://github.com/zigcc/awesome-zig][awesome-zig]] 中的实际项目,同步他们的进展。 - -虽然名字是『Zig 日报』,但应该不会每天都发,毕竟 Zig 社区还比较年轻,但估计间隔不会超过 3 天,看后续运行实际效果再来调整频率。 - -主要参与人员:西瓜、金中甲 -* 社区互动 -目前我们的成员在 Zig 的实践方面相对较少,因此决定目前不过多的去宣传,在积攒了一些实际项目经验后,再来考虑。 - -* 欢迎更多朋友加入 ZigCC -现在回看,距离第一次 ZigCC 线上会议过了一个月,经过 ZigCC 成员的努力,还是交出了一份比较满意的答卷,[[https://github.com/zigcc/zig-cookbook][cookbook]] 项目斩获 400+ 的⭐️,而且我们也有了新的 [[https://github.com/zigcc/logo][logo]],另外要感谢金中甲同学,他把之前自己写的教程捐给了 ZigCC,质量非常高,因此我们决定把他重命名为 [[https://zigcc.github.io/zig-course/][Zig 语言圣经]],熟悉 Rust 的朋友可能会知道原因。😃 - -也许在读文章的你也在犹豫是否能加入,担心没有 Zig 经验是否会有影响,其实这都不是核心,现在的成员也没说 Zig 经验有多丰富,只要有踏实做事的心态,愿意帮助他人即可,Zig 可以慢慢学,有想法的朋友可以邮件到 zig@liujiacai.net ,简单自我介绍,之后我会拉到对应群组中,便于开展后续的工作。 diff --git a/content/post/news/2024-04-27-release-party-review.org b/content/post/news/2024-04-27-release-party-review.org deleted file mode 100644 index f7c1aa0..0000000 --- a/content/post/news/2024-04-27-release-party-review.org +++ /dev/null @@ -1,35 +0,0 @@ -#+TITLE: 0.12.0 Release Party 回顾 -#+DATE: 2024-04-28T09:53:45+0800 -#+LASTMOD: 2024-08-18T12:03:29+0800 -#+TAGS[]: community -#+AUTHOR: Jiacai Liu - -2024-04-20,0.12.0 终于发布了,历时 8 个月,有 268 位贡献者,一共进行了 3688 次提交!下面是它的 Release notes: -- https://ziglang.org/download/0.12.0/release-notes.html - -ZigCC 对这个文档进行了翻译、整理,供需要升级适配的朋友参考: -- [[https://course.ziglang.cc/update/upgrade-0.12.0][0.12.0 升级指南]] -- [[https://course.ziglang.cc/update/0.12.0-description][0.12.0 版本说明]] - -为了庆祝这一盛事,ZigCC 决定在 2024-04-27 举行了一次线上的发行聚会,主要来讨论这次的版本,下面是视频回看地址: -- https://youtu.be/H0IqBNsH-9M -- https://www.bilibili.com/video/BV1Nb421Y7WX/ - -在这次会议上,主要讨论了两部分内容: - -第一是构建系统,0.12.0 版本对用户来说,主要是稳定了构建系统的 API,这对于 Zig 生态的构建十分重要,如果某用户写了一个基础库,但是升级 Zig 版本后,就没法编译了,可以想象,这是很沮丧的事情。 - -Zig 的构建系统分为两部分: -- zon 文件,声明依赖, =zig fetch= 会去下载里面的依赖 -- =build.zig= 文件,项目的构建器,由多个 Step 形成一个有向无环图,来驱动不同逻辑的进行,如安装头文件、编译静态链接库等。Step 里面最重要的是 Compile ,addTest、addExecutable 返回的都是它,主要功能是对代码进行编译。其他常见的 Step 还有 - - ConfigHeader 配置要用的头文件 - - InstallArtifact,将编译好的 lib 或 bin 安装到 zig-out 目录中 - -第二个是自己写的 x86 的后端,它可以不依赖 llvm 直接生成可以执行的汇编代码,这也是 [[https://github.com/ziglang/zig/issues/16270][make the main zig executable no longer depend on LLVM, LLD, and Clang libraries #16270]] -这个 issue 的基础。之前笔者以为所谓移除 llvm,是把 Zig 代码翻译成 C 代码,然后再有不同架构下的 C 编译器来生成最终的可执行文件,目前看这种想法是错误的, -尽管 Zig 有 C 这个后端,但目前看并不是解决这个 issue 专用的。 - -这就不得不好奇,Zig 团队难道要把生成所有架构下的二进制?还是说对于用的少的架构,[[https://github.com/ziglang/zig/issues/13265][直接生成 llvm 的 bc 文件]],然后剩下的活再交给 llvm 去做? -目前笔者还没有十分明确的答案,希望今后能尽快搞清楚这个问题,也欢迎了解的读者留言指出。 - -稍微遗憾的是这次参会的朋友基本都还是处于观望阶段,希望下次能有些具体项目经验可以聊,See you next time! diff --git a/content/post/news/_index.org b/content/post/news/_index.org deleted file mode 100644 index 0ba1e8c..0000000 --- a/content/post/news/_index.org +++ /dev/null @@ -1,11 +0,0 @@ -#+TITLE: 社区新闻 -#+DATE: 2024-08-03T16:43:51+0800 -#+LASTMOD: 2024-11-08T22:24:13+0800 -#+TYPE: docs - -我们会不定期举行线上会议来畅聊 Zig,感兴趣的朋友可以通过下面日历查看,或订阅[[https://calendar.yandex.com/export/ics.xml?private_token=71fd8e02d7944f4e7ae44cc8a9b8877da9e9f2f1&tz_id=Asia/Hong_Kong][这个 iCalendar]]。 - -#+BEGIN_EXPORT html - -#+END_EXPORT -线上会议地址:https://discord.gg/36C7H47t47?event=1304329702512787466 diff --git a/content/post/second-post.smd b/content/post/second-post.smd new file mode 100644 index 0000000..d1b1825 --- /dev/null +++ b/content/post/second-post.smd @@ -0,0 +1,28 @@ +--- +.title = "Second Post", +.date = @date("1990-01-02T00:00:00"), +.author = "ZigCC", +.layout = "post.shtml", +.draft = false, +--- + +This second post is mainly here to show you that you can also create single file +posts for convenience. The first post contains more interesting content. + +Don't forget to read [the official SuperMD +docs](https://zine-ssg.io/docs/supermd/) to know how to *style* your content. + + +Btw this sample website also includes the JS/CSS dependencies required to render +math: + +```=mathtex +\begin{aligned} +f(t) &= \int_{-\infty}^\infty F(\omega) \cdot (-1)^{2 \omega t} \mathrm{d}\omega \\ +F(\omega) &= \int_{-\infty}^\infty f(t) \div (-1)^{2 \omega t} \mathrm{d}t \\ +\end{aligned} +``` + +This: [`(-1)^x = \cos(\pi x) + i\sin(\pi x)`]($mathtex) is an inline equation +instead! + diff --git a/convert_link_format.js b/convert_link_format.js new file mode 100644 index 0000000..cac37a4 --- /dev/null +++ b/convert_link_format.js @@ -0,0 +1,107 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// 配置 +const config = { + sourceDir: './content/learn' +}; + +// 转换链接格式 +function convertLinkFormat(content) { + // 将 $link.page('filename') 转换为 $link.sub('filename') + content = content.replace(/\$link\.page\('([^']+)'\)/g, (match, filename) => { + return `$link.sub('${filename}')`; + }); + + return content; +} + +// 处理单个文件 +function processFile(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + const convertedContent = convertLinkFormat(content); + + // 如果内容有变化,写回文件 + if (content !== convertedContent) { + fs.writeFileSync(filePath, convertedContent, 'utf8'); + console.log(`✓ Converted links in: ${filePath}`); + return true; + } else { + console.log(`- No changes needed: ${filePath}`); + return false; + } + } catch (error) { + console.error(`✗ Error processing ${filePath}:`, error.message); + return false; + } +} + +// 递归处理目录 +function processDirectory(dirPath) { + try { + const items = fs.readdirSync(dirPath); + let convertedCount = 0; + let totalCount = 0; + + for (const item of items) { + const fullPath = path.join(dirPath, item); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + // 递归处理子目录 + const result = processDirectory(fullPath); + convertedCount += result.converted; + totalCount += result.total; + } else if (item.endsWith('.smd')) { + // 处理smd文件 + const converted = processFile(fullPath); + if (converted) convertedCount++; + totalCount++; + } + } + + return { converted: convertedCount, total: totalCount }; + } catch (error) { + console.error(`✗ Error processing directory ${dirPath}:`, error.message); + return { converted: 0, total: 0 }; + } +} + +// 主函数 +function main() { + console.log('🔄 Starting link format conversion...'); + console.log(`📁 Source directory: ${config.sourceDir}`); + console.log('🔄 Converting $link.page to $link.sub'); + console.log(''); + + if (!fs.existsSync(config.sourceDir)) { + console.error(`✗ Source directory does not exist: ${config.sourceDir}`); + process.exit(1); + } + + const result = processDirectory(config.sourceDir); + + console.log(''); + console.log('📊 Conversion Summary:'); + console.log(`✓ Converted links in: ${result.converted}/${result.total} files`); + + if (result.converted > 0) { + console.log('🎉 Link format conversion completed!'); + } else { + console.log('ℹ️ No link format conversion needed'); + } +} + +// 运行脚本 +if (require.main === module) { + main(); +} + +module.exports = { + convertLinkFormat, + processFile, + processDirectory +}; \ No newline at end of file diff --git a/convert_md_to_smd.js b/convert_md_to_smd.js new file mode 100644 index 0000000..50c57d9 --- /dev/null +++ b/convert_md_to_smd.js @@ -0,0 +1,150 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// 配置 +const config = { + sourceDir: './content/learn', + author: 'Karl Seguin; ZigCC', + time: '2024', + layout: 'learn.shtml' +}; + +// 转换markdown frontmatter到smd格式 +function convertFrontmatter(content) { + const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/); + if (!frontmatterMatch) { + return content; + } + + const frontmatter = frontmatterMatch[1]; + const body = content.replace(/^---\n[\s\S]*?\n---\n/, ''); + + // 解析现有的frontmatter + const lines = frontmatter.split('\n'); + const metadata = {}; + + for (const line of lines) { + const match = line.match(/^(\w+):\s*(.+)$/); + if (match) { + metadata[match[1]] = match[2].replace(/^["']|["']$/g, ''); // 移除引号 + } + } + + // 构建新的smd frontmatter + const newFrontmatter = [ + '---', + `.title = "${metadata.title || 'Untitled'}",`, + `.date = @date("2024-01-01T00:00:00"),`, + `.author = "${config.author}",`, + `.layout = "${config.layout}",`, + `.draft = false,`, + '---', + '' + ].join('\n'); + + return newFrontmatter + body; +} + +// 转换markdown链接格式 +function convertLinks(content) { + // 转换 {{< ref "filename.md" >}} 格式到相对链接 + content = content.replace(/\{\{<\s*ref\s+"([^"]+\.md)"\s*>\}\}/g, '[$1]($1)'); + + // 转换其他可能的markdown链接格式 + // 这里可以根据需要添加更多转换规则 + + return content; +} + +// 处理单个文件 +function processFile(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + const convertedContent = convertLinks(convertFrontmatter(content)); + + // 创建新的smd文件路径 + const dir = path.dirname(filePath); + const basename = path.basename(filePath, '.md'); + const newPath = path.join(dir, `${basename}.smd`); + + // 写入新文件 + fs.writeFileSync(newPath, convertedContent, 'utf8'); + console.log(`✓ Converted: ${filePath} -> ${newPath}`); + + return true; + } catch (error) { + console.error(`✗ Error processing ${filePath}:`, error.message); + return false; + } +} + +// 递归处理目录 +function processDirectory(dirPath) { + try { + const items = fs.readdirSync(dirPath); + let successCount = 0; + let totalCount = 0; + + for (const item of items) { + const fullPath = path.join(dirPath, item); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + // 递归处理子目录 + const result = processDirectory(fullPath); + successCount += result.success; + totalCount += result.total; + } else if (item.endsWith('.md')) { + // 处理markdown文件 + const success = processFile(fullPath); + if (success) successCount++; + totalCount++; + } + } + + return { success: successCount, total: totalCount }; + } catch (error) { + console.error(`✗ Error processing directory ${dirPath}:`, error.message); + return { success: 0, total: 0 }; + } +} + +// 主函数 +function main() { + console.log('🚀 Starting markdown to smd conversion...'); + console.log(`📁 Source directory: ${config.sourceDir}`); + console.log(`👤 Author: ${config.author}`); + console.log(`📅 Time: ${config.time}`); + console.log(''); + + if (!fs.existsSync(config.sourceDir)) { + console.error(`✗ Source directory does not exist: ${config.sourceDir}`); + process.exit(1); + } + + const result = processDirectory(config.sourceDir); + + console.log(''); + console.log('📊 Conversion Summary:'); + console.log(`✓ Successfully converted: ${result.success}/${result.total} files`); + + if (result.success === result.total) { + console.log('🎉 All files converted successfully!'); + } else { + console.log('⚠️ Some files failed to convert'); + } +} + +// 运行脚本 +if (require.main === module) { + main(); +} + +module.exports = { + convertFrontmatter, + convertLinks, + processFile, + processDirectory +}; \ No newline at end of file diff --git a/convert_monthly_md_to_smd.js b/convert_monthly_md_to_smd.js new file mode 100644 index 0000000..417b9d9 --- /dev/null +++ b/convert_monthly_md_to_smd.js @@ -0,0 +1,149 @@ +#!/usr/bin/env node + +// 复制自 convert_md_to_smd.js,仅修改 sourceDir +const fs = require('fs'); +const path = require('path'); + +// 配置 +const config = { + sourceDir: './content/monthly', + author: 'ZigCC', + time: '2024', + layout: 'monthly.shtml' +}; + +// 转换 markdown frontmatter 到 smd 格式 +function convertFrontmatter(content) { + const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/); + if (!frontmatterMatch) { + return content; + } + + const frontmatter = frontmatterMatch[1]; + const body = content.replace(/^---\n[\s\S]*?\n---\n/, ''); + + // 解析现有的 frontmatter + const lines = frontmatter.split('\n'); + const metadata = {}; + + for (const line of lines) { + const match = line.match(/^(\w+):\s*(.+)$/); + if (match) { + metadata[match[1]] = match[2].replace(/^['"]|['"]$/g, ''); // 移除引号 + } + } + + // 构建新的 smd frontmatter + const newFrontmatter = [ + '---', + `.title = "${metadata.title || 'Untitled'}",`, + `.date = @date("2024-01-01T00:00:00"),`, + `.author = "${config.author}",`, + `.layout = "${config.layout}",`, + `.draft = false,`, + '---', + '' + ].join('\n'); + + return newFrontmatter + body; +} + +// 转换 markdown 链接格式 +function convertLinks(content) { + // 转换 {{< ref "filename.md" >}} 格式到相对链接 + content = content.replace(/\{\{<\s*ref\s+"([^"]+\.md)"\s*>\}\}/g, '[$1]($1)'); + + // 可添加更多规则 + return content; +} + +// 处理单个文件 +function processFile(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + const convertedContent = convertLinks(convertFrontmatter(content)); + + // 创建新的 smd 文件路径 + const dir = path.dirname(filePath); + const basename = path.basename(filePath, '.md'); + const newPath = path.join(dir, `${basename}.smd`); + + // 写入新文件 + fs.writeFileSync(newPath, convertedContent, 'utf8'); + console.log(`✓ Converted: ${filePath} -> ${newPath}`); + + return true; + } catch (error) { + console.error(`✗ Error processing ${filePath}:`, error.message); + return false; + } +} + +// 递归处理目录 +function processDirectory(dirPath) { + try { + const items = fs.readdirSync(dirPath); + let successCount = 0; + let totalCount = 0; + + for (const item of items) { + const fullPath = path.join(dirPath, item); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + // 递归处理子目录 + const result = processDirectory(fullPath); + successCount += result.success; + totalCount += result.total; + } else if (item.endsWith('.md')) { + // 处理 markdown 文件 + const success = processFile(fullPath); + if (success) successCount++; + totalCount++; + } + } + + return { success: successCount, total: totalCount }; + } catch (error) { + console.error(`✗ Error processing directory ${dirPath}:`, error.message); + return { success: 0, total: 0 }; + } +} + +// 主函数 +function main() { + console.log('🚀 Starting monthly md to smd conversion...'); + console.log(`📁 Source directory: ${config.sourceDir}`); + console.log(`👤 Author: ${config.author}`); + console.log(`📅 Time: ${config.time}`); + console.log(''); + + if (!fs.existsSync(config.sourceDir)) { + console.error(`✗ Source directory does not exist: ${config.sourceDir}`); + process.exit(1); + } + + const result = processDirectory(config.sourceDir); + + console.log(''); + console.log('📊 Conversion Summary:'); + console.log(`✓ Successfully converted: ${result.success}/${result.total} files`); + + if (result.success === result.total) { + console.log('🎉 All files converted successfully!'); + } else { + console.log('⚠️ Some files failed to convert'); + } +} + +// 运行脚本 +if (require.main === module) { + main(); +} + +module.exports = { + convertFrontmatter, + convertLinks, + processFile, + processDirectory +}; \ No newline at end of file diff --git a/convert_monthly_org_to_smd.js b/convert_monthly_org_to_smd.js new file mode 100644 index 0000000..ab6e84a --- /dev/null +++ b/convert_monthly_org_to_smd.js @@ -0,0 +1,108 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +const MONTHLY_DIR = path.join(__dirname, 'content', 'monthly'); + +// 匹配 org 标题 +const orgHeadingRegex = /^(\*{1,4})\s+(.+)$/gm; +// 匹配 org 头部 +const titleRegex = /^#\+TITLE:\s*(.+)$/m; +const dateRegex = /^#\+DATE:\s*(.+)$/m; +const lastmodRegex = /^#\+LASTMOD:\s*(.+)$/m; + +// 其他 org->md 转换规则 +function orgToSmd(text) { + // 代码块 + text = text.replace(/#\+begin_src\s+(\w+)/g, '```$1'); + text = text.replace(/#\+end_src/g, '```'); + // 引用块 + text = text.replace(/#\+begin_quote/g, '>'); + text = text.replace(/#\+end_quote/g, ''); + // 行内代码 + text = text.replace(/=([^=\n]+)=/g, '`$1`'); + // 链接 [[url][desc]] + text = text.replace(/\[\[([^\]]+)\]\[([^\]]+)\]\]/g, '[$2]($1)'); + // 链接 [[url]] + text = text.replace(/\[\[([^\]]+)\]\]/g, '<$1>'); + // 图片 [[file:xxx.png]] + text = text.replace(/\[\[file:([^\]]+)\]\]/g, '![]($1)'); + // 处理 org 标题为 # [标题]($section.id('标题')) + text = text.replace(orgHeadingRegex, (match, stars, title) => { + const level = stars.length; + // 去除标题前后的空格和特殊符号 + const cleanTitle = title.trim().replace(/'/g, "\\'"); + return `${'#'.repeat(level)} [${title}]($section.id('${cleanTitle}'))`; + }); + // 去除多余 org 宏 + text = text.replace(/^#\+\w+:.*$/gm, ''); + return text; +} + +function updateFrontmatter(content, title, date, lastmod) { + // 提取 frontmatter + const fmMatch = content.match(/^---[\s\S]*?---/); + let fm = fmMatch ? fmMatch[0] : ''; + let rest = content.replace(/^---[\s\S]*?---/, '').trimStart(); + + // 更新 .title + if (title) { + fm = fm.replace(/(\.title\s*=\s*)"[^"]*"/, `$1"${title}"`); + } + // 更新 .date + if (date) { + fm = fm.replace(/(\.date\s*=\s*)@date\("[^"]*"\)/, `$1@date("${date}")`); + } + // 更新/添加 .custom + if (lastmod) { + if (/\.custom\s*=/.test(fm)) { + fm = fm.replace(/(\.custom\s*=\s*\{)[^}]*\}/, `$1 .lastmod = "${lastmod}" }`); + } else { + fm = fm.replace(/---\s*$/, `.custom = { .lastmod = "${lastmod}" },\n---`); + } + } + return fm + '\n\n' + rest; +} + +function processFile(filePath) { + let content = fs.readFileSync(filePath, 'utf8'); + // 提取 org 头部 + const titleMatch = content.match(titleRegex); + const dateMatch = content.match(dateRegex); + const lastmodMatch = content.match(lastmodRegex); + const orgTitle = titleMatch ? titleMatch[1].trim() : ''; + const orgDate = dateMatch ? dateMatch[1].trim() : ''; + const orgLastmod = lastmodMatch ? lastmodMatch[1].trim() : ''; + + // 合并 frontmatter + let newContent = updateFrontmatter( + content, + orgTitle || undefined, + orgDate || undefined, + orgLastmod || undefined + ); + // 转换正文 org->smd + newContent = orgToSmd(newContent); + // 清理多余空行 + newContent = newContent.replace(/\n{3,}/g, '\n\n'); + fs.writeFileSync(filePath, newContent, 'utf8'); + console.log('Processed:', path.basename(filePath)); +} + +function main() { + const files = fs.readdirSync(MONTHLY_DIR).filter(f => f.endsWith('.smd')); + for (const file of files) { + processFile(path.join(MONTHLY_DIR, file)); + } +} + +if (require.main === module) { + main(); +} + +module.exports = { + orgToSmd, + updateFrontmatter, + processFile +}; \ No newline at end of file diff --git a/examples/2024-01-12-how-to-release-your-zig-applications/.tool-versions b/examples/2024-01-12-how-to-release-your-zig-applications/.tool-versions deleted file mode 100644 index c947e45..0000000 --- a/examples/2024-01-12-how-to-release-your-zig-applications/.tool-versions +++ /dev/null @@ -1 +0,0 @@ -zig 0.12.0 diff --git a/examples/2024-01-12-how-to-release-your-zig-applications/Makefile b/examples/2024-01-12-how-to-release-your-zig-applications/Makefile deleted file mode 100644 index 292a94e..0000000 --- a/examples/2024-01-12-how-to-release-your-zig-applications/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -fmt: - zig fmt --check src/*.zig - -build: fmt - zig build -Doptimize=ReleaseFast -Dstrip=true diff --git a/examples/2024-01-12-how-to-release-your-zig-applications/build.zig b/examples/2024-01-12-how-to-release-your-zig-applications/build.zig deleted file mode 100644 index b30e287..0000000 --- a/examples/2024-01-12-how-to-release-your-zig-applications/build.zig +++ /dev/null @@ -1,25 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const strip = b.option(bool, "strip", "Set to true to strip binary") orelse false; - - const exe = b.addExecutable(.{ - .name = "example", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - .strip = strip, - }); - - b.installArtifact(exe); - - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} diff --git a/examples/2024-01-12-how-to-release-your-zig-applications/build.zig.zon b/examples/2024-01-12-how-to-release-your-zig-applications/build.zig.zon deleted file mode 100644 index 8fccef7..0000000 --- a/examples/2024-01-12-how-to-release-your-zig-applications/build.zig.zon +++ /dev/null @@ -1,8 +0,0 @@ -.{ - .name = "2024-01-12-how-to-release-your-zig-applications", - .version = "0.0.0", - .dependencies = .{}, - .paths = .{ - "", - }, -} diff --git a/examples/2024-01-12-how-to-release-your-zig-applications/src/main.zig b/examples/2024-01-12-how-to-release-your-zig-applications/src/main.zig deleted file mode 100644 index f92e181..0000000 --- a/examples/2024-01-12-how-to-release-your-zig-applications/src/main.zig +++ /dev/null @@ -1,5 +0,0 @@ -const std = @import("std"); - -pub fn main() !void { - std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); -} diff --git a/examples/run-all.sh b/examples/run-all.sh deleted file mode 100755 index b181b5c..0000000 --- a/examples/run-all.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -DIR="$(dirname "$(realpath "$0")")" -for ex in `ls -d */`;do - echo "Run example ${ex}..." - cd ${DIR}/${ex} - zig build --summary all -done diff --git a/fix_links.js b/fix_links.js new file mode 100644 index 0000000..70e7f43 --- /dev/null +++ b/fix_links.js @@ -0,0 +1,134 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// 配置 +const config = { + sourceDir: './content/learn' +}; + +// 修复链接格式 +function fixLinks(content) { + // 修复 [filename.md](filename.md) 格式到 $link.sub('filename') 格式 + // 这个正则表达式匹配 [filename.md](filename.md) 格式 + content = content.replace(/\[([^.\]]+)\.md\]\(([^.\]]+)\.md\)/g, (match, filename, linkFilename) => { + // 如果文件名和链接文件名相同,转换为 $link.sub 格式 + if (filename === linkFilename) { + return `$link.sub('${filename}')`; + } + // 如果不同,保持原格式(这种情况应该很少见) + return match; + }); + + // 修复 [filename](filename.md) 格式到 $link.sub('filename') 格式 + content = content.replace(/\[([^.\]]+)\]\(([^.\]]+)\.md\)/g, (match, displayText, linkFilename) => { + // 如果显示文本和链接文件名相同,转换为 $link.sub 格式 + if (displayText === linkFilename) { + return `$link.sub('${linkFilename}')`; + } + // 如果不同,保持原格式 + return match; + }); + + // 修复 [filename.md](filename.md) 格式(文件名包含点的情况) + content = content.replace(/\[([^]]+\.md)\]\(([^)]+\.md)\)/g, (match, displayText, linkFilename) => { + // 提取文件名(去掉.md扩展名) + const filename = displayText.replace('.md', ''); + const linkFile = linkFilename.replace('.md', ''); + + if (filename === linkFile) { + return `$link.sub('${filename}')`; + } + return match; + }); + + return content; +} + +// 处理单个文件 +function processFile(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + const fixedContent = fixLinks(content); + + // 如果内容有变化,写回文件 + if (content !== fixedContent) { + fs.writeFileSync(filePath, fixedContent, 'utf8'); + console.log(`✓ Fixed links in: ${filePath}`); + return true; + } else { + console.log(`- No changes needed: ${filePath}`); + return false; + } + } catch (error) { + console.error(`✗ Error processing ${filePath}:`, error.message); + return false; + } +} + +// 递归处理目录 +function processDirectory(dirPath) { + try { + const items = fs.readdirSync(dirPath); + let fixedCount = 0; + let totalCount = 0; + + for (const item of items) { + const fullPath = path.join(dirPath, item); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + // 递归处理子目录 + const result = processDirectory(fullPath); + fixedCount += result.fixed; + totalCount += result.total; + } else if (item.endsWith('.smd')) { + // 处理smd文件 + const fixed = processFile(fullPath); + if (fixed) fixedCount++; + totalCount++; + } + } + + return { fixed: fixedCount, total: totalCount }; + } catch (error) { + console.error(`✗ Error processing directory ${dirPath}:`, error.message); + return { fixed: 0, total: 0 }; + } +} + +// 主函数 +function main() { + console.log('🔗 Starting link format fix...'); + console.log(`📁 Source directory: ${config.sourceDir}`); + console.log(''); + + if (!fs.existsSync(config.sourceDir)) { + console.error(`✗ Source directory does not exist: ${config.sourceDir}`); + process.exit(1); + } + + const result = processDirectory(config.sourceDir); + + console.log(''); + console.log('📊 Fix Summary:'); + console.log(`✓ Fixed links in: ${result.fixed}/${result.total} files`); + + if (result.fixed > 0) { + console.log('🎉 Link fixes completed!'); + } else { + console.log('ℹ️ No link fixes needed'); + } +} + +// 运行脚本 +if (require.main === module) { + main(); +} + +module.exports = { + fixLinks, + processFile, + processDirectory +}; \ No newline at end of file diff --git a/fix_links_advanced.js b/fix_links_advanced.js new file mode 100644 index 0000000..4671401 --- /dev/null +++ b/fix_links_advanced.js @@ -0,0 +1,155 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// 配置 +const config = { + sourceDir: './content/learn' +}; + +// 修复链接格式 +function fixLinks(content) { + let changes = 0; + + // 1. 修复 [filename.md](filename.md) 格式到 $link.page('filename') 格式 + content = content.replace(/\[([^.\]]+)\.md\]\(([^.\]]+)\.md\)/g, (match, filename, linkFilename) => { + if (filename === linkFilename) { + changes++; + return `$link.page('${filename}')`; + } + return match; + }); + + // 2. 修复 [filename](filename.md) 格式到 $link.page('filename') 格式 + content = content.replace(/\[([^.\]]+)\]\(([^.\]]+)\.md\)/g, (match, displayText, linkFilename) => { + if (displayText === linkFilename) { + changes++; + return `$link.page('${linkFilename}')`; + } + return match; + }); + + // 3. 修复 [filename.md](filename.md) 格式(文件名包含点的情况) + content = content.replace(/\[([^]]+\.md)\]\(([^)]+\.md)\)/g, (match, displayText, linkFilename) => { + const filename = displayText.replace('.md', ''); + const linkFile = linkFilename.replace('.md', ''); + + if (filename === linkFile) { + changes++; + return `$link.page('${filename}')`; + } + return match; + }); + + // 4. 修复 [显示文本](filename.md) 格式到 [显示文本]($link.page('filename')) 格式 + content = content.replace(/\[([^]]+)\]\(([^.\]]+)\.md\)/g, (match, displayText, linkFilename) => { + if (displayText !== linkFilename) { + changes++; + return `[${displayText}]($link.page('${linkFilename}'))`; + } + return match; + }); + + // 5. 修复 [显示文本](filename.md) 格式(文件名包含点的情况) + content = content.replace(/\[([^]]+)\]\(([^)]+\.md)\)/g, (match, displayText, linkFilename) => { + const linkFile = linkFilename.replace('.md', ''); + if (displayText !== linkFile) { + changes++; + return `[${displayText}]($link.page('${linkFile}'))`; + } + return match; + }); + + return { content, changes }; +} + +// 处理单个文件 +function processFile(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + const { content: fixedContent, changes } = fixLinks(content); + + if (changes > 0) { + fs.writeFileSync(filePath, fixedContent, 'utf8'); + console.log(`✓ Fixed ${changes} links in: ${filePath}`); + return { fixed: true, changes }; + } else { + console.log(`- No changes needed: ${filePath}`); + return { fixed: false, changes: 0 }; + } + } catch (error) { + console.error(`✗ Error processing ${filePath}:`, error.message); + return { fixed: false, changes: 0 }; + } +} + +// 递归处理目录 +function processDirectory(dirPath) { + try { + const items = fs.readdirSync(dirPath); + let fixedCount = 0; + let totalCount = 0; + let totalChanges = 0; + + for (const item of items) { + const fullPath = path.join(dirPath, item); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + // 递归处理子目录 + const result = processDirectory(fullPath); + fixedCount += result.fixed; + totalCount += result.total; + totalChanges += result.totalChanges; + } else if (item.endsWith('.smd')) { + // 处理smd文件 + const result = processFile(fullPath); + if (result.fixed) fixedCount++; + totalCount++; + totalChanges += result.changes; + } + } + + return { fixed: fixedCount, total: totalCount, totalChanges }; + } catch (error) { + console.error(`✗ Error processing directory ${dirPath}:`, error.message); + return { fixed: 0, total: 0, totalChanges: 0 }; + } +} + +// 主函数 +function main() { + console.log('🔗 Starting advanced link format fix...'); + console.log(`📁 Source directory: ${config.sourceDir}`); + console.log(''); + + if (!fs.existsSync(config.sourceDir)) { + console.error(`✗ Source directory does not exist: ${config.sourceDir}`); + process.exit(1); + } + + const result = processDirectory(config.sourceDir); + + console.log(''); + console.log('📊 Fix Summary:'); + console.log(`✓ Fixed links in: ${result.fixed}/${result.total} files`); + console.log(`🔗 Total link changes: ${result.totalChanges}`); + + if (result.fixed > 0) { + console.log('🎉 Link fixes completed!'); + } else { + console.log('ℹ️ No link fixes needed'); + } +} + +// 运行脚本 +if (require.main === module) { + main(); +} + +module.exports = { + fixLinks, + processFile, + processDirectory +}; \ No newline at end of file diff --git a/fix_zig_update_section.js b/fix_zig_update_section.js new file mode 100644 index 0000000..52caa1f --- /dev/null +++ b/fix_zig_update_section.js @@ -0,0 +1,43 @@ +const fs = require('fs'); +const path = require('path'); + +const MONTHLY_DIR = path.join(__dirname, 'content', 'monthly'); + +// 更宽松地匹配 Zig 语言更新 section 标题(允许不同空格、section id、大小写) +const zigUpdateSectionRegex = /^(#+) \[Zig[  ]*语言更新\]\(\$section\.id\(['"]?[^'")]+['"]?\)\)[\s\S]*?(?=^#|\n{2,}|\n*$)/gim; +// 匹配 github 链接和日期区间 +const githubLinkRegex = /(https:\/\/github\.com\/ziglang\/zig\/pulls\?[^)]*closed%3A(\d{4}-\d{2}-\d{2})\.\.(\d{4}-\d{2}-\d{2}))/i; + +function fixZigUpdateSection(content) { + // 找到所有 Zig 语言更新 section + return content.replace(zigUpdateSectionRegex, (sectionBlock, hashes) => { + // 在 sectionBlock 内查找 github 链接 + const linkMatch = sectionBlock.match(githubLinkRegex); + if (!linkMatch) return sectionBlock; // 没有链接则不处理 + const url = linkMatch[1]; + const date1 = linkMatch[2]; + const date2 = linkMatch[3]; + // 构造新格式 + return `${hashes} [Zig 语言更新]($section.id('zig-update'))\n[${date1}..${date2}](${url})`; + }); +} + +function processFile(filePath) { + let content = fs.readFileSync(filePath, 'utf8'); + const newContent = fixZigUpdateSection(content); + if (newContent !== content) { + fs.writeFileSync(filePath, newContent, 'utf8'); + console.log('Fixed Zig 语言更新 in:', path.basename(filePath)); + } +} + +function main() { + const files = fs.readdirSync(MONTHLY_DIR).filter(f => f.endsWith('.smd')); + for (const file of files) { + processFile(path.join(MONTHLY_DIR, file)); + } +} + +if (require.main === module) { + main(); +} \ No newline at end of file diff --git a/frpc.ini b/frpc.ini new file mode 100644 index 0000000..12485d8 --- /dev/null +++ b/frpc.ini @@ -0,0 +1,15 @@ +[common] +user = howijs6zqne9fcvt + +sakura_mode = true +login_fail_exit = false + +server_addr = frp-cup.com +server_port = 8088 + +[xtest] +# id = 21713037 +type = http +local_ip = 127.0.0.1 +local_port = 1990 +custom_domains = frp.xihale.top diff --git a/go.mod b/go.mod deleted file mode 100644 index 731646d..0000000 --- a/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module zigcc - -go 1.22.0 - -require github.com/google/docsy v0.11.1-0.20250516151613-47fd135c4b3e // indirect diff --git a/go.sum b/go.sum deleted file mode 100644 index de24a99..0000000 --- a/go.sum +++ /dev/null @@ -1,11 +0,0 @@ -github.com/FortAwesome/Font-Awesome v0.0.0-20240402185447-c0f460dca7f7/go.mod h1:IUgezN/MFpCDIlFezw3L8j83oeiIuYoj28Miwr/KUYo= -github.com/FortAwesome/Font-Awesome v0.0.0-20240716171331-37eff7fa00de/go.mod h1:IUgezN/MFpCDIlFezw3L8j83oeiIuYoj28Miwr/KUYo= -github.com/FortAwesome/Font-Awesome v0.0.0-20241216213156-af620534bfc3/go.mod h1:IUgezN/MFpCDIlFezw3L8j83oeiIuYoj28Miwr/KUYo= -github.com/google/docsy v0.10.0 h1:6tMDacPwAyRWNCfvsn/9qGOZDQ8b0aRzjRZvnZPY5dg= -github.com/google/docsy v0.10.0/go.mod h1:c0nIAqmRTOuJ01F85U/wJPQtc3Zj9N58Kea9bOT2AJc= -github.com/google/docsy v0.11.0 h1:QnV40cc28QwS++kP9qINtrIv4hlASruhC/K3FqkHAmM= -github.com/google/docsy v0.11.0/go.mod h1:hGGW0OjNuG5ZbH5JRtALY3yvN8ybbEP/v2iaK4bwOUI= -github.com/google/docsy v0.11.1-0.20250516151613-47fd135c4b3e h1:HfmBArN3/h9Q385iNWQpyeeuHmE93S3YinzrxnETfx8= -github.com/google/docsy v0.11.1-0.20250516151613-47fd135c4b3e/go.mod h1:1bioDqA493neyFesaTvQ9reV0V2vYy+xUAnlnz7+miM= -github.com/twbs/bootstrap v5.3.3+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0= -github.com/twbs/bootstrap v5.3.6+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0= diff --git a/layouts/blog.shtml b/layouts/blog.shtml new file mode 100644 index 0000000..ff41a2f --- /dev/null +++ b/layouts/blog.shtml @@ -0,0 +1,25 @@ + + + + + +

+
+
+ \ No newline at end of file diff --git a/.gitmodules b/layouts/community.shtml similarity index 100% rename from .gitmodules rename to layouts/community.shtml diff --git a/layouts/contributing.shtml b/layouts/contributing.shtml new file mode 100644 index 0000000..e69de29 diff --git a/layouts/index.shtml b/layouts/index.shtml new file mode 100644 index 0000000..9cfbdff --- /dev/null +++ b/layouts/index.shtml @@ -0,0 +1,7 @@ + + + + + +
+ \ No newline at end of file diff --git a/layouts/learn.shtml b/layouts/learn.shtml new file mode 100644 index 0000000..aa8b4a8 --- /dev/null +++ b/layouts/learn.shtml @@ -0,0 +1,27 @@ + + + + + + +
+ +

Table of Contents

+
+
+
+ + \ No newline at end of file diff --git a/layouts/monthly.shtml b/layouts/monthly.shtml new file mode 100644 index 0000000..5ce6ac0 --- /dev/null +++ b/layouts/monthly.shtml @@ -0,0 +1,25 @@ + + + + +

+

Section Contents

+
    +
  • +
+
+ + \ No newline at end of file diff --git a/layouts/page.shtml b/layouts/page.shtml new file mode 100644 index 0000000..54e79c5 --- /dev/null +++ b/layouts/page.shtml @@ -0,0 +1,7 @@ + + + + +

+
+ \ No newline at end of file diff --git a/layouts/partials/hooks/head-end.html b/layouts/partials/hooks/head-end.html deleted file mode 100644 index e76fbe3..0000000 --- a/layouts/partials/hooks/head-end.html +++ /dev/null @@ -1,15 +0,0 @@ - - diff --git a/layouts/post.shtml b/layouts/post.shtml new file mode 100644 index 0000000..db6c5b4 --- /dev/null +++ b/layouts/post.shtml @@ -0,0 +1,40 @@ + + + + + +

+
+ + \ No newline at end of file diff --git a/layouts/post.xml b/layouts/post.xml new file mode 100644 index 0000000..b9c2b25 --- /dev/null +++ b/layouts/post.xml @@ -0,0 +1,19 @@ + + + + + + Zine -- https://zine-ssg.io + en-US + + + + + + + + + + + + diff --git a/layouts/templates/base.shtml b/layouts/templates/base.shtml new file mode 100644 index 0000000..90bc0ca --- /dev/null +++ b/layouts/templates/base.shtml @@ -0,0 +1,54 @@ + + + + + + + + + + + + +
+

+ +
+ + + + \ No newline at end of file diff --git a/main b/main new file mode 160000 index 0000000..e7911e6 --- /dev/null +++ b/main @@ -0,0 +1 @@ +Subproject commit e7911e6cc6408d2955bf3683a7b2c46577f6d780 diff --git a/public/about/index.html b/public/about/index.html new file mode 100644 index 0000000..d716282 --- /dev/null +++ b/public/about/index.html @@ -0,0 +1,61 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ +

About Zine

Zine is an MIT-licensed project created by Loris Cro and other contributors listed on the official repository.

Zine is inspired by Hugo but features an entirely custom set of authoring languages:

  • Scripty is the small expression language that both SuperHTML and SuperMD share to express templating logic.

  • SuperHTML is the HTML templating language used by Zine. Unlike most {{curly braced}} templating languages, SuperHTML uses valid HTML syntax to express the templating logic, adding only minor extensions to normal HTML.
    Thanks to this approach, it offers instant syntax checking and autoformatting via a CLI tool as well as Language Server support (VScode Extension).

    NOTE

    The correct file extension for SuperHTML templates is .shtml.

  • SuperMD is a superset of Markdown that, instead of relying on inline HTML, offers new constructs for expressing content embeds without pulling into your content needless layouting concerns. A CLI tool and language server for SuperMD is in the works.

    NOTE

    The correct file extension for SuperMD pages is .smd.

Zine is alpha software

Zine is not yet complete. The main functionality is present and you will be able to build even moderately complex static websites without issue.

That said using Zine today does imply participating in the development process to some degree, which usually means inquiring about the development status of a feature you need, or reporting a bug.

Here are some quicklinks related to Zine:

+ + + + \ No newline at end of file diff --git a/public/community/index.html b/public/community/index.html new file mode 100644 index 0000000..aace79f --- /dev/null +++ b/public/community/index.html @@ -0,0 +1,61 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ +
+ + + + \ No newline at end of file diff --git a/public/contributing/index.html b/public/contributing/index.html new file mode 100644 index 0000000..aace79f --- /dev/null +++ b/public/contributing/index.html @@ -0,0 +1,61 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ +
+ + + + \ No newline at end of file diff --git a/public/highlight.css b/public/highlight.css new file mode 100644 index 0000000..f2c4c4a --- /dev/null +++ b/public/highlight.css @@ -0,0 +1,37 @@ +:root { + --light-yellow: #e5c07b; + --dark-yellow: #d19a66; + --blue: #61afef; + --cyan: #56b6c2; + --light-red: #e06c75; + --dark-red: #be5046; + --comment-gray: #5c6370; + --magenta: #c678dd; +} + +pre { + border-top: 1px solid white; + border-bottom: 1px solid white; + padding: 10px 5px; +} + +code.ziggy { + color: var(--cyan); +} + +code.ziggy .keyword, +code.ziggy .type { + color: var(--light-yellow); +} + +code.ziggy .string { + color: var(--dark-yellow); +} + +code.ziggy .numeric.constant { + color: var(--magenta); +} + +code.ziggy .function { + color: var(--blue); +} \ No newline at end of file diff --git a/public/index.css b/public/index.css new file mode 100644 index 0000000..ae96e01 --- /dev/null +++ b/public/index.css @@ -0,0 +1,9 @@ +.content{ + font-size: 1.5rem; + display: flex; + flex-direction: column; + align-items: center; +} +#logo{ + width: 40vw; +} \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..a3bd7e1 --- /dev/null +++ b/public/index.html @@ -0,0 +1,61 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ +

Zig 中文社区致力于在中文用户中分享和传播 Zig 语言!

我们是一群对 Zig 编程语言充满热情的开发者、学习者和爱好者。我们致力于:

  1. 分享 Zig 相关的知识和经验
  2. 翻译 Zig 官方文档和重要资源
  3. 组织线上线下的学习活动和讨论
  4. 推广 Zig 语言在中文开发者中的应用

无论你是 Zig 专家还是初学者,我们都欢迎你的加入

+ + + + \ No newline at end of file diff --git a/public/learn/coding-in-zig/index.html b/public/learn/coding-in-zig/index.html new file mode 100644 index 0000000..176bcfc --- /dev/null +++ b/public/learn/coding-in-zig/index.html @@ -0,0 +1,525 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ + +
+ +

Table of Contents

+
    +
  • 悬空指针 Dangling Pointers
  • 所有权 Ownership
  • ArrayList
  • Anytype
  • @TypeOf
  • 构建系统
  • 第三方依赖
+
+

原文地址:https://www.openmymind.net/learning_zig/coding_in_zig

在介绍了 Zig 语言的大部分内容之后,我们将对一些主题进行回顾,并展示几种使用 Zig 编程时一些实用的技巧。在此过程中,我们将介绍更多的标准库,并介绍一些稍复杂些的代码片段。

悬空指针 Dangling Pointers

我们首先来看看更多关于悬空指针的例子。这似乎是一个奇怪的问题,但如果你之前主要使用带垃圾回收的语言,这可能是你学习 Zig 最大的障碍。

你能猜到下面的输出是什么吗?

const std = @import("std");
+
+pub fn main() !void {
+	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+	const allocator = gpa.allocator();
+
+	var lookup = std.StringHashMap(User).init(allocator);
+	defer lookup.deinit();
+
+	const goku = User{.power = 9001};
+
+	try lookup.put("Goku", goku);
+
+	// returns an optional, .? would panic if "Goku"
+	// wasn't in our hashmap
+	const entry = lookup.getPtr("Goku").?;
+
+	std.debug.print("Goku's power is: {d}\n", .{entry.power});
+
+	// returns true/false depending on if the item was removed
+	_ = lookup.remove("Goku");
+
+	std.debug.print("Goku's power is: {d}\n", .{entry.power});
+}
+
+const User = struct {
+	power: i32,
+};
+
+

当我运行这个程序时,我得到了

Goku's power is: 9001
+Goku's power is: -1431655766
+
+

这段代码引入了 Zig 的 std.StringHashMap,它是 std.AutoHashMap 的特定版本,键类型设置为 []const u8。即使你不能百分百确定发生了什么,也可以猜测我的输出与我们从 lookup 中删除条目后的第二次打印有关。注释掉删除的调用,输出就正常了。

理解上述代码的关键在于了解数据在内存的中位置,或者换句话说,了解数据的所有者。请记住,Zig 参数是按值传递的,也就是说,我们传递的是值的浅副本。我们 lookup 中的 Usergoku 引用的内存不同。我们上面的代码有两个用户,每个用户都有自己的所有者。goku 的所有者是 main,而它的副本的所有者是 lookup

getPtr 方法返回的是指向 map 中值的指针,在我们的例子中,它返回的是 *User。问题就在这里,删除会使我们的 entry指针失效。在这个示例中,getPtrremove 的位置很近,因此问题也很明显。但不难想象,代码在调用 remove 时,并不知道 entry 的引用被保存在其他地方了。

在编写这个示例时,我并不确定会发生什么。删除有可能是通过设置内部标志来实现的,实际删除是惰性的。如果是这样的话,上面的示例在简单的情况下可能会 “奏效”,但在更复杂的情况下就会失败。这听起来非常难以调试。

除了不调用 remove 之外,我们还可以用几种不同的方法来解决这个问题。首先,我们可以使用 get 而不是 getPtr。这样 lookup 将返回一个 User 的副本,而不再是 *User。这样我们就有了三个用户:

  1. 定义在函数内部的 gokumain 函数是其所有者
  2. 调用 lookup.put 时,形式参数会得到 goku 一个的副本,lookup 是其所有者
  3. 使用 get 函数返回的 entrymain 函数是其所有者

由于 entry 现在是 User 的独立副本,因此将其从 lookup 中删除不会再使其失效。

另一种方法是将 lookup 的类型从 StringHashMap(User) 改为 StringHashMap(*const User)。这段代码可以工作:

const std = @import("std");
+
+pub fn main() !void {
+	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+	const allocator = gpa.allocator();
+
+	// User -> *const User
+	var lookup = std.StringHashMap(*const User).init(allocator);
+	defer lookup.deinit();
+
+	const goku = User{.power = 9001};
+
+	// goku -> &goku
+	try lookup.put("Goku", &goku);
+
+	// getPtr -> get
+	const entry = lookup.get("Goku").?;
+
+	std.debug.print("Goku's power is: {d}\n", .{entry.power});
+	_ = lookup.remove("Goku");
+	std.debug.print("Goku's power is: {d}\n", .{entry.power});
+}
+
+const User = struct {
+	power: i32,
+};
+
+

上述代码中有许多微妙之处。首先,我们现在只有一个用户 gokulookupentry 中的值都是对 goku 的引用。我们对 remove 的调用仍然会删除 lookup 中的值,但该值只是 user 的地址,而不是 user 本身。如果我们坚持使用 getPtr,那么被 remove 后,我们就会得到一个无效的 **User。在这两种解决方案中,我们都必须使用 get 而不是 getPtr,但在这种情况下,我们只是复制地址,而不是完整的 User。对于占用内存较多的对象来说,这可能是一个很大的区别。

如果把所有东西都放在一个函数中,再加上一个像 User 这样的小值,这仍然像是一个人为制造的问题。我们需要一个能让数据所有权成为当务之急的例子。

所有权 Ownership

我喜欢哈希表(HashMap),因为这是每个人都知道并且会经常使用的结构。它们有很多不同的用例,其中大部分你可能都用过。虽然哈希表可以用在一个短期查找的地方,但通常用于长期查找,因此插入其内的值需要同样长的生命周期。

这段代码将使用终端中输入的名称来填充我们的 lookup。如果名字为空,就会停止提示循环。最后,它会检测 Leto 是否出现在 lookup 中。

const std = @import("std");
+const builtin = @import("builtin");
+
+pub fn main() !void {
+	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+	const allocator = gpa.allocator();
+
+	var lookup = std.StringHashMap(User).init(allocator);
+	defer lookup.deinit();
+
+	// stdin is an std.io.Reader
+	// the opposite of an std.io.Writer, which we already saw
+	const stdin = std.io.getStdIn().reader();
+
+	// stdout is an std.io.Writer
+	const stdout = std.io.getStdOut().writer();
+
+	var i: i32 = 0;
+	while (true) : (i += 1) {
+		var buf: [30]u8 = undefined;
+		try stdout.print("Please enter a name: ", .{});
+		if (try stdin.readUntilDelimiterOrEof(&buf, '\n')) |line| {
+			var name = line;
+			// Windows平台换行以`\r\n`结束
+			// 所以需要截取\r以获取控制台输入字符
+			if (builtin.os.tag == .windows) {
+			    name = @constCast(std.mem.trimRight(u8, name, "\r"));
+			}
+
+			if (name.len == 0) {
+				break;
+			}
+			try lookup.put(name, .{.power = i});
+		}
+	}
+
+	const has_leto = lookup.contains("Leto");
+	std.debug.print("{any}\n", .{has_leto});
+}
+
+const User = struct {
+	power: i32,
+};
+
+

上述代码虽然区分大小写,但无论我们如何完美地输入 Letocontains 总是返回 false。让我们通过遍历 lookup 打印其值来调试一下:

// 将这段代码放在 while 循环之后
+
+var it = lookup.iterator();
+while (it.next()) |kv| {
+	std.debug.print("{s} == {any}\n", .{kv.key_ptr.*, kv.value_ptr.*});
+}
+
+
+

这种迭代器模式在 Zig 中很常见,它依赖于 while 和可选类型(Optional)之间的协同作用。我们的迭代器返回指向键和值的指针,因此我们用 .* 对它们进行反引用,以访问实际值而不是地址。输出结果将取决于你输入的内容,但我得到的是

Please enter a name: Paul
+Please enter a name: Teg
+Please enter a name: Leto
+Please enter a name:
+
+�� == learning.User{ .power = 1 }
+
+��� == learning.User{ .power = 0 }
+
+��� == learning.User{ .power = 2 }
+false
+
+

值看起来没问题,但键不一样。如果你不确定发生了什么,那可能是我的错。之前,我故意误导了你的注意力。我说哈希表通常声明周期会比较长,因此需要同等生命周期的值(value)。事实上,哈希表不仅需要长生命周期的值,还需要长生命周期的键(key)!请注意,buf 是在 while 循环中定义的。当我们调用 put 时,我们给了哈希表插入一个键值对,这个键的生命周期比哈希表本身短得多。将 buf 移到 while 循环之外可以解决生命周期问题,但每次迭代都会重复使用缓冲区。由于我们正在更改底层的键数据,因此它仍然无法工作。

对于上述代码,实际上只有一种解决方案:我们的 lookup 必须拥有键的所有权。我们需要添加一行并修改另一行:

// 用这两行替换现有的 lookup.put
+const owned_name = try allocator.dupe(u8, name);
+
+// name -> owned_name
+try lookup.put(owned_name, .{.power = i});
+
+

dupestd.mem.Allocator 中的一个方法,我们以前从未见过。它会分配给定值的副本。代码现在可以工作了,因为我们的键现在在堆上,比 lookup的生命周期更长。事实上,我们在延长这些字符串的生命周期方面做得太好了,以至于引入了内存泄漏。

你可能以为当我们调用 lookup.deinit 时,键和值就会被释放。但 StringHashMap 并没有放之四海而皆准的解决方案。首先,键可能是字符串文字,无法释放。其次,它们可能是用不同的分配器创建的。最后,虽然更先进,但在某些情况下,键可能不属于哈希表。

唯一的解决办法就是自己释放键值。在这一点上,创建我们自己的 UserLookup 类型并在 deinit 函数中封装这一清理逻辑可能会比较合理。一种简单的改法:

// 用以下的代码替换现有的 defer lookup.deinit();
+defer {
+	var it = lookup.keyIterator();
+	while (it.next()) |key| {
+		allocator.free(key.*);
+	}
+	lookup.deinit();
+}
+
+

这里的 defer 逻辑使用了一个代码快,它释放每个键,最后去释放 lookup 本身。我们使用的 keyIterator 只会遍历键。迭代器的值是指向哈希映射中键的指针,即 *[]const u8。我们希望释放实际的值,因为这是我们通过 dupe 分配的,所以我们使用 key.*.

我保证,关于悬挂指针和内存管理的讨论已经结束了。我们所讨论的内容可能还不够清晰或过于抽象。当你有更实际的问题需要解决时,再重新讨论这个问题也不迟。不过,如果你打算编写任何稍具规模(non-trivial)的程序,这几乎肯定是你需要掌握的内容。当你觉得可以的时候,我建议你参考上面这个示例,并自己动手实践一下。引入一个 UserLookup 类型来封装我们必须做的所有内存管理。尝试使用 *User 代替 User,在堆上创建用户,然后像处理键那样释放它们。编写覆盖新结构的测试,使用 std.testing.allocator 确保不会泄漏任何内存。

ArrayList

现在你可以忘掉我们的 IntList 和我们创建的通用替代方案了。Zig 标准库中有一个动态数组实现:std.ArrayList(T)

它是相当标准的东西,但由于它如此普遍需要和使用的数据结构,值得看看它的实际应用:

const std = @import("std");
+const Allocator = std.mem.Allocator;
+const builtin = @import("builtin");
+
+pub fn main() !void {
+	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+	const allocator = gpa.allocator();
+
+	var arr = std.ArrayList(User).init(allocator);
+	defer {
+		for (arr.items) |user| {
+			user.deinit(allocator);
+		}
+		arr.deinit();
+	}
+
+	// stdin is an std.io.Reader
+	// the opposite of an std.io.Writer, which we already saw
+	const stdin = std.io.getStdIn().reader();
+
+	// stdout is an std.io.Writer
+	const stdout = std.io.getStdOut().writer();
+
+	var i: i32 = 0;
+	while (true) : (i += 1) {
+		var buf: [30]u8 = undefined;
+		try stdout.print("Please enter a name: ", .{});
+		if (try stdin.readUntilDelimiterOrEof(&buf, '\n')) |line| {
+			var name = line;
+			if (builtin.os.tag == .windows) {
+				name = @constCast(std.mem.trimRight(u8, name, "\r"));
+			}
+
+			if (name.len == 0) {
+				break;
+			}
+			const owned_name = try allocator.dupe(u8, name);
+			try arr.append(.{.name = owned_name, .power = i});
+		}
+	}
+
+	var has_leto = false;
+	for (arr.items) |user| {
+		if (std.mem.eql(u8, "Leto", user.name)) {
+			has_leto = true;
+			break;
+		}
+	}
+
+	std.debug.print("{any}\n", .{has_leto});
+}
+
+const User = struct {
+	name: []const u8,
+	power: i32,
+
+	fn deinit(self: User, allocator: Allocator) void {
+		allocator.free(self.name);
+	}
+};
+
+

以上是哈希表代码的基于 ArrayList(User) 的另一种实现。所有相同的生命周期和内存管理规则都适用。请注意,我们仍在创建 name 的副本,并且仍在删除 ArrayList 之前释放每个 name

现在是指出 Zig 没有属性或私有字段的好时机。当我们访问 arr.items 来遍历值时,就可以看到这一点。没有属性的原因是为了消除阅读 Zig 代码中的歧义。在 Zig 中,如果看起来像字段访问,那就是字段访问。我个人认为,没有私有字段是一个错误,但我们可以解决这个问题。我已经习惯在字段前加上下划线,表示『仅供内部使用』。

由于字符串的类型是 []u8[]const u8,因此 ArrayList(u8) 是字符串构造器的合适类型,比如 .NET 的 StringBuilder 或 Go 的 strings.Builder。事实上,当一个函数的参数是 Writer 而你需要一个字符串时,就会用到 ArrayList(u8)。我们之前看过一个使用 std.json.stringify 将 JSON 输出到 stdout 的示例。下面是将 JSON 输出到 ArrayList(u8) 的示例:

const std = @import("std");
+
+pub fn main() !void {
+	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+	const allocator = gpa.allocator();
+
+	var out = std.ArrayList(u8).init(allocator);
+	defer out.deinit();
+
+	try std.json.stringify(.{
+		.this_is = "an anonymous struct",
+		.above = true,
+		.last_param = "are options",
+	}, .{.whitespace = .indent_2}, out.writer());
+
+	std.debug.print("{s}\n", .{out.items});
+}
+
+

Anytype

语言概述的第一部分中,我们简要介绍了 anytype。这是一种非常有用的编译时 duck 类型。下面是一个简单的 logger:

pub const Logger = struct {
+	level: Level,
+
+	// "error" is reserved, names inside an @"..." are always
+	// treated as identifiers
+	const Level = enum {
+		debug,
+		info,
+		@"error",
+		fatal,
+	};
+
+	fn info(logger: Logger, msg: []const u8, out: anytype) !void {
+		if (@intFromEnum(logger.level) <= @intFromEnum(Level.info)) {
+			try out.writeAll(msg);
+		}
+	}
+};
+
+

info 函数的 out 参数类型为 anytype。这意味着我们的 logger 可以将信息输出到任何具有 writeAll 方法的结构中,该方法接受一个 []const u8 并返回一个 !void。这不是运行时特性。类型检查在编译时进行,每使用一种类型,就会创建一个类型正确的函数。如果我们试图调用 info,而该类型不具备所有必要的函数(本例中只有 writeAll),我们就会在编译时出错:

var l = Logger{.level = .info};
+try l.info("sever started", true);
+
+

会得到如下错误:

no field or member function named 'writeAll' in 'bool'
+
+

使用 ArrayList(u8)writer 就可以运行:

pub fn main() !void {
+	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+	const allocator = gpa.allocator();
+
+	var l = Logger{.level = .info};
+
+	var arr = std.ArrayList(u8).init(allocator);
+	defer arr.deinit();
+
+	try l.info("sever started", arr.writer());
+	std.debug.print("{s}\n", .{arr.items});
+}
+
+

anytype 的一个最大缺点就是文档。下面是我们用过几次的 std.json.stringify 函数的签名:

// 我**讨厌**多行函数定义
+// 不过,鉴于你可能在小屏幕上阅读这个指南,因此这里破一次例。
+
+fn stringify(
+	value: anytype,
+	options: StringifyOptions,
+	out_stream: anytype
+) @TypeOf(out_stream).Error!void
+
+

第一个参数 value: anytype 是显而易见的,它是要序列化的值,可以是任何类型(实际上,Zig 的 JSON 序列化器不能序列化某些类型,比如 HashMap)。我们可以猜测,out_stream 是写入 JSON 的地方,但至于它需要实现什么方法,你和我一样猜得到。唯一的办法就是阅读源代码,或者传递一个假值,然后使用编译器错误作为我们的文档。如果有更好的自动文档生成器,这一点可能会得到改善。不过,我希望 Zig 能提供接口,这已经不是第一次了。

@TypeOf

在前面的部分中,我们使用 @TypeOf 来帮助我们检查各种变量的类型。从我们的用法来看,你可能会认为它返回的是字符串类型的名称。然而,鉴于它是一个 PascalCase 风格函数,你应该更清楚:它返回的是一个 type

我最喜欢用 anytype@TypeOf@hasField 内置函数搭配使用,以编写测试帮助程序。虽然我们看到的每个 User 类型都非常简单,但我还是要请大家想象一下一个有很多字段的更复杂的结构。在许多测试中,我们需要一个 User,但我们只想指定与测试相关的字段。让我们创建一个 userFactory

fn userFactory(data: anytype) User {
+	const T = @TypeOf(data);
+	return .{
+		.id = if (@hasField(T, "id")) data.id else 0,
+		.power = if (@hasField(T, "power")) data.power else 0,
+		.active  = if (@hasField(T, "active")) data.active else true,
+		.name  = if (@hasField(T, "name")) data.name else "",
+	};
+}
+
+pub const User = struct {
+	id: u64,
+	power: u64,
+	active: bool,
+	name: [] const u8,
+};
+
+

我们可以通过调用 userFactory(.{}) 创建默认用户,也可以通过 userFactory(.{.id = 100, .active = false}) 来覆盖特定字段。这只是一个很小的模式,但我非常喜欢。这也是迈向元编程世界的第一步。

更常见的是 @TypeOf@typeInfo 配对,后者返回一个 std.builtin.Type。这是一个功能强大的带标签的联合(tagged union),可以完整描述一个类型。std.json.stringify 函数会递归地调用它,以确定如何将提供的 value 序列化。

构建系统

如果你通读了整本指南,等待着深入了解如何建立更复杂的项目,包括多个依赖关系和各种目标,那你就要失望了。Zig 拥有强大的构建系统,以至于越来越多的非 Zig 项目都在使用它,比如 libsodium。不幸的是,所有这些强大的功能都意味着,对于简单的需求来说,它并不是最容易使用或理解的。

事实上,是我不太了解 Zig 的构建系统,所以无法解释清楚。

不过,我们至少可以获得一个简要的概述。为了运行 Zig 代码,我们使用了 zig run learning.zig。有一次,我们还用 zig test learning.zig 进行了一次测试。运行和测试命令用来玩玩还行,但如果要做更复杂的事情,就需要使用构建命令了。编译命令依赖于带有特殊编译入口的 build.zig 文件。下面是一个示例:

// build.zig
+
+const std = @import("std");
+
+pub fn build(b: *std.Build) !void {
+	_ = b;
+}
+
+

每个构建程序都有一个默认的『安装』步骤,可以使用 zig build install 运行它,但由于我们的文件大部分是空的,你不会得到任何有意义的工件。我们需要告诉构建程序我们程序的入口是 learning.zig

const std = @import("std");
+
+pub fn build(b: *std.Build) !void {
+	const target = b.standardTargetOptions(.{});
+	const optimize = b.standardOptimizeOption(.{});
+
+	// setup executable
+	const exe = b.addExecutable(.{
+		.name = "learning",
+		.target = target,
+		.optimize = optimize,
+		.root_source_file = b.path("learning.zig"),
+	});
+	b.installArtifact(exe);
+}
+
+

现在,如果运行 zig build install,就会在 ./zig-out/bin/learning 中得到一个二进制文件。通过使用 standardTargetOptionsstandardOptimizeOption,我们就能以命令行参数的形式覆盖默认值。例如,要为 Windows 构建一个大小优化的程序版本,我们可以这样做:

zig build install -Doptimize=ReleaseSmall -Dtarget=x86_64-windows-gnu
+
+

除了默认的『安装』步骤外,可执行文件通常还会增加两个步骤:『运行』和『测试』。一个库可能只有一个『测试』步骤。对于基本的无参数即可运行的程序来说,只需要在构建文件的最后添加四行:

// 在这行代码后添加下面的代码: b.installArtifact(exe);
+
+const run_cmd = b.addRunArtifact(exe);
+run_cmd.step.dependOn(b.getInstallStep());
+
+const run_step = b.step("run", "Start learning!");
+run_step.dependOn(&run_cmd.step);
+
+

这里通过 dependOn 的两次调用创建两个依赖关系。第一个依赖关系将我们的 run_cmd 与内置的安装步骤联系起来。第二个是将 run_step 与我们新创建的 run_cmd 绑定。你可能想知道为什么需要 run_cmdrun_step。我认为这种分离是为了支持更复杂的设置:依赖于多个命令的步骤,或者在多个步骤中使用的命令。如果运行 zig build --help 并滚动到顶部,你会看到新增的 run 步骤。现在你可以执行 zig build run 来运行程序了。

要添加『测试』步骤,你需要重复刚才添加的大部分运行代码,只是不再使用 b.addExecutable,而是使用 b.addTest

const tests = b.addTest(.{
+	.target = target,
+	.optimize = optimize,
+	.root_source_file = b.path("learning.zig"),
+});
+
+const test_cmd = b.addRunArtifact(tests);
+test_cmd.step.dependOn(b.getInstallStep());
+const test_step = b.step("test", "Run the tests");
+test_step.dependOn(&test_cmd.step);
+
+

我们将该步骤命名为 test。运行 zig build --help 会显示另一个可用步骤 test。由于我们没有进行任何测试,因此很难判断这一步是否有效。在 learning.zig 中,添加

test "dummy build test" {
+	try std.testing.expectEqual(false, true);
+}
+
+

现在运行 zig build test时,应该会出现测试失败。如果你修复了测试,并再次运行 zig build test,你将不会得到任何输出。默认情况下,Zig 的测试运行程序只在失败时输出结果。如果你像我一样,无论成功还是失败,都想要一份总结,那就使用 zig build test --summary all

这是启动和运行构建系统所需的最低配置。但是请放心,如果你需要构建你的程序,Zig 内置的功能大概率能覆盖你的需求。最后,你可以(也应该)在你的项目根目录下使用 zig init,让 Zig 为你创建一个文档齐全的 build.zig 文件。

第三方依赖

Zig 的内置软件包管理器相对较新,因此存在一些缺陷。虽然还有改进的余地,但它目前还是可用的。我们需要了解两个部分:创建软件包和使用软件包。我们将对其进行全面介绍。

首先,新建一个名为 calc 的文件夹并创建三个文件。第一个是 add.zig,内容如下:

// 哦,下面的函数定义中有语法之前没讲过,看看 b 的类型和返回类型!!
+
+pub fn add(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
+	return a + b;
+}
+
+const testing = @import("std").testing;
+test "add" {
+	try testing.expectEqual(@as(i32, 32), add(30, 2));
+}
+
+

这个例子可能看起来有点傻,一整个软件包只是为了加两个数值,但它能让我们专注于打包方面。接下来,我们将添加一个同样愚蠢的:calc.zig

pub const add = @import("add.zig").add;
+
+test {
+	// By default, only tests in the specified file
+	// are included. This magic line of code will
+	// cause a reference to all nested containers
+	// to be tested.
+	@import("std").testing.refAllDecls(@This());
+}
+
+

我们将其分割为 calc.zigadd.zig,以证明 zig build 可以自动构建和打包所有项目文件。最后,我们可以添加 build.zig:

const std = @import("std");
+
+pub fn build(b: *std.Build) !void {
+	const target = b.standardTargetOptions(.{});
+	const optimize = b.standardOptimizeOption(.{});
+
+	const tests = b.addTest(.{
+		.target = target,
+		.optimize = optimize,
+		.root_source_file = b.path("calc.zig"),
+	});
+
+	const test_cmd = b.addRunArtifact(tests);
+	test_cmd.step.dependOn(b.getInstallStep());
+	const test_step = b.step("test", "Run the tests");
+	test_step.dependOn(&test_cmd.step);
+}
+
+

这些都是我们在上一节中看到的内容的重复。有了这些,你就可以运行 zig build test --summary all

回到我们的 learning项目和之前创建的 build.zig。首先,我们将添加本地 calc 作为依赖项。我们需要添加三项内容。首先,我们将创建一个指向 calc.zig的模块:

// 你可以把这些代码放在构建函数的顶部,
+// 即调用 addExecutable 之前。
+
+const calc_module = b.addModule("calc", .{
+	.root_source_file = b.path("PATH_TO_CALC_PROJECT/calc.zig"),
+});
+
+

你需要调整 calc.zig 的路径。现在,我们需要将这个模块添加到现有的 exetests 变量中。由于我们的 build.zig 变得越来越复杂,我们将尝试稍微组织一下:

const std = @import("std");
+
+pub fn build(b: *std.Build) !void {
+	const target = b.standardTargetOptions(.{});
+	const optimize = b.standardOptimizeOption(.{});
+
+	const calc_module = b.addModule("calc", .{
+		.root_source_file = b.path("PATH_TO_CALC_PROJECT/calc.zig"),
+	});
+
+	{
+		// 设置我们的 "run" 命令。
+
+		const exe = b.addExecutable(.{
+			.name = "learning",
+			.target = target,
+			.optimize = optimize,
+			.root_source_file = b.path("learning.zig"),
+		});
+		// 添加这些代码
+		exe.root_module.addImport("calc", calc_module);
+		b.installArtifact(exe);
+
+		const run_cmd = b.addRunArtifact(exe);
+		run_cmd.step.dependOn(b.getInstallStep());
+
+		const run_step = b.step("run", "Start learning!");
+		run_step.dependOn(&run_cmd.step);
+	}
+
+	{
+		// 设置我们的 "test" 命令。
+		const tests = b.addTest(.{
+			.target = target,
+			.optimize = optimize,
+			.root_source_file = b.path("learning.zig"),
+		});
+		// 添加这行代码
+		tests.root_module.addImport("calc", calc_module);
+
+		const test_cmd = b.addRunArtifact(tests);
+		test_cmd.step.dependOn(b.getInstallStep());
+		const test_step = b.step("test", "Run the tests");
+		test_step.dependOn(&test_cmd.step);
+	}
+}
+
+

现在,可以在项目中 @import("calc")

const calc = @import("calc");
+...
+calc.add(1, 2);
+
+

添加远程依赖关系需要花费更多精力。首先,我们需要回到 calc 项目并定义一个模块。你可能认为项目本身就是一个模块,但一个项目(project)可以暴露多个模块(module),所以我们需要明确地创建它。我们使用相同的 addModule,但舍弃了返回值。只需调用 addModule 就足以定义模块,然后其他项目就可以导入该模块。

_ = b.addModule("calc", .{
+	.root_source_file = b.path("calc.zig"),
+});
+
+

这是我们需要对库进行的唯一改动。因为这是一个远程依赖的练习,所以我把这个 calc 项目推送到了 GitHub,这样我们就可以把它导入到我们的 learning 项目中。它可以在 https://github.com/karlseguin/calc.zig 上找到。

回到我们的 learning项目,我们需要一个新文件 build.zig.zon。ZON 是 Zig Object Notation 的缩写,它允许以人类可读格式表达 Zig 数据,并将人类可读格式转化为 Zig 代码。build.zig.zon 的内容包括:

.{
+  .name = "learning",
+  .paths = .{""},
+  .version = "0.0.0",
+  .dependencies = .{
+    .calc = .{
+      .url = "https://github.com/karlseguin/calc.zig/archive/d1881b689817264a5644b4d6928c73df8cf2b193.tar.gz",
+      .hash = "12ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+    },
+  },
+}
+
+

该文件中有两个可疑值,第一个是 url 中的 d1881b689817264a5644b4d6928c73df8cf2b193<。这只是 git 提交的哈希值。第二个是哈希值。据我所知,目前还没有很好的方法来告诉我们这个值应该是多少,所以我们暂时使用一个假值。

要使用这一依赖关系,我们需要对 build.zig 进行一处修改:

// 将这些代码:
+const calc_module = b.addModule("calc", .{
+	.root_source_file = b.path("calc/calc.zig"),
+});
+
+// 替换成:
+const calc_dep = b.dependency("calc", .{.target = target,.optimize = optimize});
+const calc_module = calc_dep.module("calc");
+
+

build.zig.zon 中,我们将依赖关系命名为 calc,这就是我们要加载的依赖关系。在这个依赖关系中,我们将使用其中的 calc 模块,也就是我们在 calcbuild.zig.zon 中命名的模块。

如果你尝试运行 zig build test,应该会看到一个错误:

hash mismatch: manifest declares
+122053da05e0c9348d91218ef015c8307749ef39f8e90c208a186e5f444e818672da
+
+but the fetched package has
+122036b1948caa15c2c9054286b3057877f7b152a5102c9262511bf89554dc836ee5
+
+

将正确的哈希值复制并粘贴回 build.zig.zon,然后再次尝试运行 zig build test,现在一切应该都正常了。

听起来很多,我希望能精简一些。但这主要是你可以从其他项目中复制和粘贴的东西,一旦设置完成,你就可以继续了。

需要提醒的是,我发现 Zig 对依赖项的缓存偏激。如果你试图更新依赖项,但 Zig 似乎检测不到变化。这时,我会删除项目的 zig-cache 文件夹以及 ~/.cache/zig


我们已经涉猎了很多领域,探索了一些核心数据结构,并将之前的大块内容整合到了一起。我们的代码变得复杂了一些,不再那么注重特定的语法,看起来更像真正的代码。让我感到兴奋的是,尽管如此复杂,但代码大部分都是有意义的。如果暂时没有看懂,也不要放弃。选取一个示例并将其分解,添加打印语句,为其编写一些测试。亲自动手编写自己的代码,然后再回来阅读那些没有看懂的部分。

+ + + + \ No newline at end of file diff --git a/public/learn/conclusion/index.html b/public/learn/conclusion/index.html new file mode 100644 index 0000000..6529dfc --- /dev/null +++ b/public/learn/conclusion/index.html @@ -0,0 +1,63 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ + +
+

原文总结:https://www.openmymind.net/learning_zig/conclusion

有些读者可能会认出我是各种『The Little $TECH Book』 的作者(译者注:原作者还写过 The Little Go BookThe Little MongoDB Book),并想知道为什么这本书不叫『The Little Zig Book』。事实上,我不确定 Zig 是否适合『小』这个范畴。部分挑战在于,Zig 的复杂性和学习曲线会因个人背景和经验的不同而大相径庭。如果你是一个经验丰富的 C 或 C++ 程序员,那么简明扼要地总结一下这门语言可能就够了,这种情况下你可能会更需要Zig 的官方文档

虽然我们在本指南中涉及了很多内容,但仍有大量内容我们尚未触及。我不希望这让你气馁或不知所措。所有语言的学习都是循序渐进的,通过本教程,你有了一个良好基础,也可以把它当作参考资料,可以开始学习 Zig 语言中更高级的功能。坦率地说,我没有涉及的部分我本身就理解有限,因此无法很好的解释。但这并不妨碍我使用 Zig 编写有意义的东西,比如一个流行的 HTTP 服务器

最后,我想强调一件完全被略过的事情,你之前可能有所耳闻,即 Zig 与 C 代码交互非常容易。因为 Zig 的生态还很年轻,标准库也很小,所以在某些情况下,使用 C 库可能是最好的选择。例如,Zig 标准库中没有正则表达式模块,使用 C 语言库就是一个合理的选择。我曾为 SQLite 和 DuckDB 编写过 Zig 库,这很简单。如果你基本遵循了本指南中的所有内容,应该不会有任何问题。

希望本资料对你有所帮助,也希望你能在编程过程中获得乐趣。

+ + + + \ No newline at end of file diff --git a/public/learn/generics/index.html b/public/learn/generics/index.html new file mode 100644 index 0000000..5e9f0d1 --- /dev/null +++ b/public/learn/generics/index.html @@ -0,0 +1,214 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ + +
+

原文地址:https://www.openmymind.net/learning_zig/generics

在上一小节中,我们创建了一个名为 IntList 的动态数组。该数据结构的目标是保存数目不定的数值。虽然我们使用的算法适用于任何类型的数据,但我们的实现与 i64 值绑定。这就需要使用泛型,其目的是从特定类型中抽象出算法和数据结构。

许多语言使用特殊的语法和特定的泛型规则来实现泛型。而在 Zig 中,泛型并不是一种特定的功能,而更多地体现了语言的能力。具体来说,泛型利用了 Zig 强大的编译时元编程功能。

我们先来看一个简单的例子,以了解我们的想法:

const std = @import("std");
+
+pub fn main() !void {
+	var arr: IntArray(3) = undefined;
+	arr[0] = 1;
+	arr[1] = 10;
+	arr[2] = 100;
+	std.debug.print("{any}\n", .{arr});
+}
+
+fn IntArray(comptime length: usize) type {
+	return [length]i64;
+}
+
+

上述代码会打印了 { 1, 10, 100 }。有趣的是,我们有一个返回类型的函数(因此函数是 PascalCase)。这也不是普通的类型,而是由函数参数动态确定的类型。这段代码之所以能运行,是因为我们将 length 声明为 comptime。也就是说,我们要求任何调用 IntArray 的人传递一个编译时已知的长度参数。这是必要的,因为我们的函数返回一个类型,而类型必须始终是编译时已知的。

函数可以返回任何类型,而不仅仅是基本类型和数组。例如,只需稍作改动,我们就可以让函数返回一个结构体:

const std = @import("std");
+
+pub fn main() !void {
+	var arr: IntArray(3) = undefined;
+	arr.items[0] = 1;
+	arr.items[1] = 10;
+	arr.items[2] = 100;
+	std.debug.print("{any}\n", .{arr.items});
+}
+
+fn IntArray(comptime length: usize) type {
+	return struct {
+		items: [length]i64,
+	};
+}
+
+

也许看起来很奇怪,但 arr 的类型确实是 IntArray(3)。它和其他类型一样,是一个类型,而 arr 和其他值一样,是一个值。如果我们调用 IntArray(7),那就是另一种类型了。也许我们可以让事情变得更简洁:

const std = @import("std");
+
+pub fn main() !void {
+	var arr = IntArray(3).init();
+	arr.items[0] = 1;
+	arr.items[1] = 10;
+	arr.items[2] = 100;
+	std.debug.print("{any}\n", .{arr.items});
+}
+
+fn IntArray(comptime length: usize) type {
+	return struct {
+		items: [length]i64,
+
+		fn init() IntArray(length) {
+			return .{
+				.items = undefined,
+			};
+		}
+	};
+}
+
+

乍一看,这可能并不整齐。但除了匿名和嵌套在一个函数中之外,我们的结构看起来就像我们目前看到的其他结构一样。它有字段,有函数。你知道人们常说『如果它看起来像一只鸭子,那么就就是一只鸭子』。那么,这个结构看起来、游起来和叫起来都像一个正常的结构,因为它本身就是一个结构体。

希望上面这个示例能让你熟悉返回类型的函数和相应的语法。为了得到一个更典型的范型,我们需要做最后一个改动:我们的函数必须接受一个类型。实际上,这只是一个很小的改动,但 type 会比 usize 更抽象,所以我们慢慢来。让我们进行一次飞跃,修改之前的 IntList,使其能与任何类型一起工作。我们先从基本结构开始:

fn List(comptime T: type) type {
+	return struct {
+		pos: usize,
+		items: []T,
+		allocator: Allocator,
+
+		fn init(allocator: Allocator) !List(T) {
+			return .{
+				.pos = 0,
+				.allocator = allocator,
+				.items = try allocator.alloc(T, 4),
+			};
+		}
+	};
+}
+
+

上面的结构与 IntList 几乎完全相同,只是 i64 被替换成了 T。我们本可以叫它 item_type。不过,按照 Zig 的命名约定,type 类型的变量使用 PascalCase 风格。

无论好坏,使用单个字母表示类型参数的历史都比 Zig 要悠久得多。在大多数语言中,T 是常用的默认值,但你也会看到根据具体语境而变化的情况,例如哈希映射使用 K 和 V 来表示键和值参数类型。

如果你对上述代码还有疑问,可以着重看使用 T 的两个地方:items:[]Tallocator.alloc(T, 4)。当我们要使用这个通用类型时,我们将使用

var list = try List(u32).init(allocator);
+
+

编译代码时,编译器会通过查找每个 T 并将其替换为 u32 来创建一个新类型。如果我们再次使用 List(u32),编译器将重新使用之前创建的类型。如果我们为 T 指定一个新值,例如 List(bool)List(User),就会创建与之对应的新类型。

为了完成通用的 List,我们可以复制并粘贴 IntList 代码的其余部分,然后用 T 替换 i64

const std = @import("std");
+const Allocator = std.mem.Allocator;
+
+pub fn main() !void {
+	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+	const allocator = gpa.allocator();
+
+	var list = try List(u32).init(allocator);
+	defer list.deinit();
+
+	for (0..10) |i| {
+		try list.add(@intCast(i));
+	}
+
+	std.debug.print("{any}\n", .{list.items[0..list.pos]});
+}
+
+fn List(comptime T: type) type {
+	return struct {
+		pos: usize,
+		items: []T,
+		allocator: Allocator,
+
+		fn init(allocator: Allocator) !List(T) {
+			return .{
+				.pos = 0,
+				.allocator = allocator,
+				.items = try allocator.alloc(T, 4),
+			};
+		}
+
+		fn deinit(self: List(T)) void {
+			self.allocator.free(self.items);
+		}
+
+		fn add(self: *List(T), value: T) !void {
+			const pos = self.pos;
+			const len = self.items.len;
+
+			if (pos == len) {
+				// we've run out of space
+				// create a new slice that's twice as large
+				var larger = try self.allocator.alloc(T, len * 2);
+
+				// copy the items we previously added to our new space
+				@memcpy(larger[0..len], self.items);
+
+				self.allocator.free(self.items);
+
+				self.items = larger;
+			}
+
+			self.items[pos] = value;
+			self.pos = pos + 1;
+		}
+	};
+}
+
+

我们的 init 函数返回一个 List(T),我们的 deinitadd 函数使用 List(T)*List(T) 作为参数。在我们的这个简单的示例中,这样做没有问题,但对于大型数据结构,编写完整的通用名称可能会变得有点繁琐,尤其是当我们有多个类型参数时(例如,散列映射的键和值需要使用不同的类型)。@This() 内置函数会返回它被调用时的最内层类型。一般来说,我们会这样定义 List(T)

fn List(comptime T: type) type {
+	return struct {
+		pos: usize,
+		items: []T,
+		allocator: Allocator,
+
+		// Added
+		const Self = @This();
+
+		fn init(allocator: Allocator) !Self {
+			// ... same code
+		}
+
+		fn deinit(self: Self) void {
+			// .. same code
+		}
+
+		fn add(self: *Self, value: T) !void {
+			// .. same code
+		}
+	};
+}
+
+

Self 并不是一个特殊的名称,它只是一个变量,而且是 PascalCase 风格,因为它的值是一种类型。我们可以在之前使用 List(T) 的地方用 Self 来替代。


我们可以创建更复杂的示例,使用多种类型参数和更先进的算法。但归根结底,泛型代码的关键点与上述简单示例相差无几。在下一部分,我们将在研究标准库中的 ArrayList(T)StringHashMap(V) 时再次讨论泛型。

+ + + + \ No newline at end of file diff --git a/public/learn/heap-memory-and-allocator/index.html b/public/learn/heap-memory-and-allocator/index.html new file mode 100644 index 0000000..8d11044 --- /dev/null +++ b/public/learn/heap-memory-and-allocator/index.html @@ -0,0 +1,444 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ + +
+ +

Table of Contents

+
    +
  • defer 和 errdefer
  • 双重释放和内存泄漏
  • 创建与销毁
  • 分配器 Allocator
  • 通用分配器 GeneralPurposeAllocator
  • std.testing.allocator
  • ArenaAllocator
  • 固定缓冲区分配器 FixedBufferAllocator
+
+

原文地址:https://www.openmymind.net/learning_zig/heap_memory

迄今为止,我们所接触到的一切都有个限制,需要预先知道大小。数组总是有一个编译时已知的长度(事实上,长度是类型的一部分)。我们所有的字符串都是字符串字面量,其长度在编译时是已知的。

此外,我们所见过的两种内存管理策略,即全局数据调用栈,虽然简单高效,但都有局限性。这两种策略都无法处理动态大小的数据,而且在数据生命周期方面都很固定。

本部分分为两个主题。第一个主题是第三个内存区域–堆的总体概述。另一个主题是 Zig 直接而独特的堆内存管理方法。即使你熟悉堆内存,比如使用过 C 语言的 malloc,你也会希望阅读第一部分,因为它是 Zig 特有的。

堆是我们可以使用的第三个也是最后一个内存区域。与全局数据和调用栈相比,堆有点像蛮荒之地:什么都可以使用。具体来说,在堆中,我们可以在运行时创建大小已知的内存,并完全控制其生命周期。

调用堆栈之所以令人惊叹,是因为它管理数据的方式简单且可预测(通过压入和弹出堆栈帧)。这一优点同时也是缺点:数据的生命周期与它在调用堆栈中的位置息息相关。堆则恰恰相反。它没有内置的生命周期,因此我们的数据可长可短。这个优点也是它的缺点:它没有内置的生命周期,所以如果我们不释放数据,就没有人会释放。

让我们来看一个例子:

const std = @import("std");
+
+pub fn main() !void {
+	// we'll be talking about allocators shortly
+	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+	const allocator = gpa.allocator();
+
+	// ** The next two lines are the important ones **
+	var arr = try allocator.alloc(usize, try getRandomCount());
+	defer allocator.free(arr);
+
+	for (0..arr.len) |i| {
+		arr[i] = i;
+	}
+	std.debug.print("{any}\n", .{arr});
+}
+
+fn getRandomCount() !u8 {
+	var seed: u64 = undefined;
+	try std.posix.getrandom(std.mem.asBytes(&seed));
+	var random = std.Random.DefaultPrng.init(seed);
+	return random.random().uintAtMost(u8, 5) + 5;
+}
+
+

我们稍后将讨论 Zig 的分配器,目前需要知道的是分配器是 std.mem.Allocator 类型。我们使用了它的两种方法:allocfree。分配内存可能出错,故我们用 try 捕获调用 allocator.alloc 产生的错误。目前唯一可能的错误是 OutOfMemory。其参数主要告诉我们它是如何工作的:它需要一个类型(T)和一个计数,成功时返回一个类型为 []T 的切片。它分配发生在运行时期间,必须如此,因为我们的计数只在运行时才可知。

一般来说,每次 alloc 都会有相应的 freealloc分配内存,free释放内存。不要让这段简单的代码限制了你的想象力。这种 try alloc + defer free 的模式很常见,这是有原因的:在我们分配内存的地方附近释放相对来说是万无一失的。但同样常见的是在一个地方分配,而在另一个地方释放。正如我们之前所说,堆没有内置的生命周期管理。你可以在 HTTP 处理程序中分配内存,然后在后台线程中释放,这是代码中两个完全独立的部分。

defer 和 errdefer

说句题外话,上面的代码介绍了一个新的语言特性:defer,它在退出作用域时执行给定的代码。『作用域退出』包括到达作用域的结尾或从作用域返回。严格来说, defer 与分配器或内存管理并无严格关系;你可以用它来执行任何代码。但上述用法很常见。

Zig 的 defer 类似于 Go 的 defer,但存在一个主要区别。在 Zig 中,defer 将在其包含作用域的末尾运行。在 Go 中,defer 是在包含函数的末尾运行。除非你是 Go 开发人员,否则 Zig 的做法可能更不令人惊讶。

defer 相似的是 errdefer,它作用与之类似,是在退出作用域时执行给定的代码,但只在返回错误时执行。在进行更复杂的设置时,如果因为出错而不得不撤销之前的分配,这将非常有用。

以下示例在复杂性上有所增加。它展示了 errdefer 和一个常见的模式,即在 init 函数中分配内存,并在 deinit 中释放:

const std = @import("std");
+const Allocator = std.mem.Allocator;
+
+pub const Game = struct {
+	players: []Player,
+	history: []Move,
+	allocator: Allocator,
+
+	fn init(allocator: Allocator, player_count: usize) !Game {
+		var players = try allocator.alloc(Player, player_count);
+		errdefer allocator.free(players);
+
+		// store 10 most recent moves per player
+		var history = try allocator.alloc(Move, player_count * 10);
+
+		return .{
+			.players = players,
+			.history = history,
+			.allocator = allocator,
+		};
+	}
+
+	fn deinit(game: Game) void {
+		const allocator = game.allocator;
+		allocator.free(game.players);
+		allocator.free(game.history);
+	}
+};
+
+

这段代码主要突显两件事:

  1. errdefer 的作用。在正常情况下,playerinit 分配,在 deinit 释放。但有一种边缘情况,即 history 初始化失败。在这种情况下,我们需要撤销 players 的分配。
  2. 我们动态分配的两个切片(playershistory)的生命周期是基于我们的应用程序逻辑的。没有任何规则规定何时必须调用 deinit 或由谁调用。这是件好事,因为它为我们提供了任意的生命周期,但也存在缺点,就是如果从未调用 deinit 或调用 deinit 超过一次,就会出现混乱和错误。

initdeinit 的名字并不特殊。它们只是 Zig 标准库使用的,也是社区采纳的名称。在某些情况下,包括在标准库中,会使用 openclose,或其他更适当的名称。

双重释放和内存泄漏

上面提到过,没有规则规定什么时候必须释放什么东西。但事实并非如此,还是有一些重要规则,只是它们不是强制的,需要你自己格外小心。

第一条规则是不可释放同一内存两次。

const std = @import("std");
+
+pub fn main() !void {
+	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+	const allocator = gpa.allocator();
+
+	var arr = try allocator.alloc(usize, 4);
+	allocator.free(arr);
+	allocator.free(arr);
+
+	std.debug.print("This won't get printed\n", .{});
+}
+
+

可以预见到,最后一行代码不会被打印出来。这是因为我们释放了相同的内存两次。这被称为双重释放,是无效的。要避免这种情况似乎很简单,但在具有复杂生命周期的大型项目中,却很难发现。

第二条规则是,无法释放没有引用的内存。这听起来似乎很明显,但谁负责释放内存并不总是很清楚。下面的代码声明了一个转小写的函数:

const std = @import("std");
+const Allocator = std.mem.Allocator;
+
+fn allocLower(allocator: Allocator, str: []const u8) ![]const u8 {
+	var dest = try allocator.alloc(u8, str.len);
+
+	for (str, 0..) |c, i| {
+		dest[i] = switch (c) {
+			'A'...'Z' => c + 32,
+			else => c,
+		};
+	}
+
+	return dest;
+}
+
+

上面的代码没问题。但以下用法不是:

// 对于这个特定的代码,我们应该使用 std.ascii.eqlIgnoreCase
+fn isSpecial(allocator: Allocator, name: [] const u8) !bool {
+	const lower = try allocLower(allocator, name);
+	return std.mem.eql(u8, lower, "admin");
+}
+
+

这是内存泄漏。allocLower 中创建的内存永远不会被释放。不仅如此,一旦 isSpecial 返回,这块内存就永远无法释放。在有垃圾收集器的语言中,当数据变得无法访问时,垃圾收集器最终会释放无用的内存。

但在上面的代码中,一旦 isSpecial 返回,我们就失去了对已分配内存的唯一引用,即 lower 变量。而直到我们的进程退出后,这块内存块才会释放。我们的函数可能只会泄漏几个字节,但如果它是一个长时间运行的进程,并且重复调用该函数,未被释放的内存块就会逐渐累积起来,最终会耗尽所有内存。

至少在双重释放的情况下,我们的程序会遭遇严重崩溃。内存泄漏可能很隐蔽。不仅仅是根本原因难以确定。真正的小泄漏或不常执行的代码中的泄漏甚至很难被发现。这是一个很常见的问题,Zig 提供了帮助,我们将在讨论分配器时看到。

创建与销毁

std.mem.Allocatoralloc方法会返回一个切片,其长度为传递的第二个参数。如果想要单个值,可以使用 createdestroy 而不是 allocfree

前面几部分在学习指针时,我们创建了 User 并尝试增强它的功能。下面是基于堆的版本:

const std = @import("std");
+
+pub fn main() !void {
+	// again, we'll talk about allocators soon!
+	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+	const allocator = gpa.allocator();
+
+	// create a User on the heap
+	var user = try allocator.create(User);
+
+	// free the memory allocated for the user at the end of this scope
+	defer allocator.destroy(user);
+
+	user.id = 1;
+	user.power = 100;
+
+	// this line has been added
+	levelUp(user);
+	std.debug.print("User {d} has power of {d}\n", .{user.id, user.power});
+}
+
+fn levelUp(user: *User) void {
+	user.power += 1;
+}
+
+pub const User = struct {
+	id: u64,
+	power: i32,
+};
+
+

create 方法接受一个参数,类型(T)。它返回指向该类型的指针或一个错误,即 !*T。也许你想知道,如果我们创建了User, 但没有设置 id, power时会发生什么。这就像将这些字段设置为未定义(undefined),其行为也是未定义的。意即,属性没有初始化时,在访问未初始化的变量,行为也是未定义,这意味着程序可能会出现不可预测的行为,比如返回错误的值、崩溃等问题。

当我们探索悬空指针时,函数错误地返回了本地user的地址:

pub const User = struct {
+	fn init(id: u64, power: i32) *User{
+		var user = User{
+			.id = id,
+			.power = power,
+		};
+		// this is a dangling pointer
+		return &user;
+	}
+};
+
+

在这种情况下,返回一个 User可能更有意义。但有时你会希望函数返回一个指向它所创建的东西的指针。当你想让生命周期不受调用栈的限制时,你就会这样做。为了解决上面的悬空指针问题,我们可以使用create 方法:

// 我们的返回类型改变了,因为 init 现在可以失败了
+// *User -> !*User
+fn init(allocator: std.mem.Allocator, id: u64, power: i32) !*User{
+	const user = try allocator.create(User);
+	user.* = .{
+		.id = id,
+		.power = power,
+	};
+	return user;
+}
+
+

我引入了新的语法,user.* = .{...}。这有点奇怪,我不是很喜欢它,但你会看到它。右侧是你已经见过的内容:它是一个带有类型推导的结构体初始化器。我们可以明确地使用 user.* = User{...}。左侧的 user.* 是我们如何去引用该指针所指向的变量。& 接受一个 T 类型并给我们一个 *T 类型。.* 是相反的操作,应用于一个 *T 类型的值时,它给我们一个 T 类型。即,&获取地址,.*获取值。

请记住,create 返回一个 !*User,所以我们的 user*User 类型。

分配器 Allocator

Zig 的核心原则之一是无隐藏内存分配。根据你的背景,这听起来可能并不特别。但这与 C 语言中使用标准库的 malloc 函数分配内存的做法形成了鲜明的对比。在 C 语言中,如果你想知道一个函数是否分配内存,你需要阅读源代码并查找对 malloc 的调用。

Zig 没有默认的分配器。在上述所有示例中,分配内存的函数都使用了一个 std.mem.Allocator 参数。按照惯例,这通常是第一个参数。所有 Zig 标准库和大多数第三方库都要求调用者在分配内存时提供一个分配器。

这种显式性有两种形式。在简单的情况下,每次函数调用都会提供分配器。这样的例子很多,但 std.fmt.allocPrint 是你迟早会用到的一个。它类似于我们一直在使用的 std.debug.print,只是分配并返回一个字符串,而不是将其写入 stderr

const say = std.fmt.allocPrint(allocator, "It's over {d}!!!", .{user.power});
+defer allocator.free(say);
+
+

另一种形式是将 Allocator 传递给 init ,然后由对象内部使用。这种方法不那么明确,因为你已经给了对象一个分配器来使用,但你不知道哪些方法调用将实际分配。对于长寿命对象来说,这种方法更实用。

注入分配器的优势不仅在于显式,还在于灵活性。std.mem.Allocator 是一个接口,提供了 allocfreecreatedestroy 函数以及其他一些函数。到目前为止,我们只看到了 std.heap.GeneralPurposeAllocator,但标准库或第三方库中还有其他实现。

Zig 没有用于创建接口的语法糖。一种类似于接口的模式是带标签的联合(tagged unions),不过与真正的接口相比,这种模式相对受限。整个标准库中也探索了一些其他模式,例如 std.mem.Allocator。本指南不探讨这些接口模式。

如果你正在构建一个库,那么最好接受一个 std.mem.Allocator,然后让库的用户决定使用哪种分配器实现。否则,你就需要选择正确的分配器,正如我们将看到的,这些分配器并不相互排斥。在你的程序中创建不同的分配器可能有很好的理由。

通用分配器 GeneralPurposeAllocator

顾名思义,std.heap.GeneralPurposeAllocator 是一种通用的、线程安全的分配器,可以作为应用程序的主分配器。对于许多程序来说,这是唯一需要的分配器。程序启动时,会创建一个分配器并传递给需要它的函数。我的 HTTP 服务器库中的示例代码就是一个很好的例子:

const std = @import("std");
+const httpz = @import("httpz");
+
+pub fn main() !void {
+	// create our general purpose allocator
+	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+
+	// get an std.mem.Allocator from it
+	const allocator = gpa.allocator();
+
+	// pass our allocator to functions and libraries that require it
+	var server = try httpz.Server().init(allocator, .{.port = 5882});
+
+	var router = server.router();
+	router.get("/api/user/:id", getUser);
+
+	// blocks the current thread
+	try server.listen();
+}
+
+

我们创建了 GeneralPurposeAllocator,从中获取一个 std.mem.Allocator 并将其传递给 HTTP 服务器的 init 函数。在一个更复杂的项目中,声明的变量allocator 可能会被传递给代码的多个部分,每个部分可能都会将其传递给自己的函数、对象和依赖。

你可能会注意到,创建 gpa 的语法有点奇怪。什么是GeneralPurposeAllocator(.{}){}

我们之前见过这些东西,只是现在都混合了起来。std.heap.GeneralPurposeAllocator 是一个函数,由于它使用的是 PascalCase(帕斯卡命名法),我们知道它返回一个类型。(下一部分会更多讨论泛型)。也许这个更明确的版本会更容易解读:

const T = std.heap.GeneralPurposeAllocator(.{});
+var gpa = T{};
+
+// 等同于:
+
+var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+
+

也许你仍然不太确信 .{} 的含义。我们之前也见过它:.{} 是一个具有隐式类型的结构体初始化器。

类型是什么,字段在哪里?类型其实是 std.heap.general_purpose_allocator.Config,但它并没有直接暴露出来,这也是我们没有显式给出类型的原因之一。没有设置字段是因为 Config 结构定义了默认值,我们将使用默认值。这是配置、选项的中常见的模式。事实上,我们在下面几行向 init 传递 .{.port = 5882} 时又看到了这种情况。在本例中,除了端口这一个字段外,我们都使用了默认值。

std.testing.allocator

希望当我们谈到内存泄漏时,你已经足够烦恼,而当我提到 Zig 可以提供帮助时,你肯定渴望了解更多这方面内容。这种帮助来自 std.testing.allocator,它是一个 std.mem.Allocator 实现。目前,它基于通用分配器(GeneralPurposeAllocator)实现,并与 Zig 的测试运行器进行了集成,但这只是实现细节。重要的是,如果我们在测试中使用 std.testing.allocator,就能捕捉到大部分内存泄漏。

你可能已经熟悉了动态数组(通常称为 ArrayLists)。在许多动态编程语言中,所有数组都是动态的。动态数组支持可变数量的元素。Zig 有一个通用 ArrayList,但我们将创建一个专门用于保存整数的 ArrayList,并演示泄漏检测:

pub const IntList = struct {
+	pos: usize,
+	items: []i64,
+	allocator: Allocator,
+
+	fn init(allocator: Allocator) !IntList {
+		return .{
+			.pos = 0,
+			.allocator = allocator,
+			.items = try allocator.alloc(i64, 4),
+		};
+	}
+
+	fn deinit(self: IntList) void {
+		self.allocator.free(self.items);
+	}
+
+	fn add(self: *IntList, value: i64) !void {
+		const pos = self.pos;
+		const len = self.items.len;
+
+		if (pos == len) {
+			// we've run out of space
+			// create a new slice that's twice as large
+			var larger = try self.allocator.alloc(i64, len * 2);
+
+			// copy the items we previously added to our new space
+			@memcpy(larger[0..len], self.items);
+
+			self.items = larger;
+		}
+
+		self.items[pos] = value;
+		self.pos = pos + 1;
+	}
+};
+
+

有趣的部分发生在 add 函数里,当 pos == len时,表明我们已经填满了当前数组,并且需要创建一个更大的数组。我们可以像这样使用IntList

const std = @import("std");
+const Allocator = std.mem.Allocator;
+
+pub fn main() !void {
+	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+	const allocator = gpa.allocator();
+
+	var list = try IntList.init(allocator);
+	defer list.deinit();
+
+	for (0..10) |i| {
+		try list.add(@intCast(i));
+	}
+
+	std.debug.print("{any}\n", .{list.items[0..list.pos]});
+}
+
+

代码运行并打印出正确的结果。不过,尽管我们在 list 上调用了 deinit,还是出现了内存泄漏。如果你没有发现也没关系,因为我们要写一个测试,并使用 std.testing.allocator

const testing = std.testing;
+test "IntList: add" {
+	// We're using testing.allocator here!
+	var list = try IntList.init(testing.allocator);
+	defer list.deinit();
+
+	for (0..5) |i| {
+		try list.add(@intCast(i+10));
+	}
+
+	try testing.expectEqual(@as(usize, 5), list.pos);
+	try testing.expectEqual(@as(i64, 10), list.items[0]);
+	try testing.expectEqual(@as(i64, 11), list.items[1]);
+	try testing.expectEqual(@as(i64, 12), list.items[2]);
+	try testing.expectEqual(@as(i64, 13), list.items[3]);
+	try testing.expectEqual(@as(i64, 14), list.items[4]);
+}
+
+

@as 是一个执行类型强制的内置函数。如果你好奇什么我们的测试要用到这么多,那么你不是唯一一个。从技术上讲,这是因为第二个参数,即 actual,被强制为第一个参数,即 expected。在上面的例子中,我们的期望值都是 comptime_int,这就造成了问题。包括我在内的许多人都认为这是一种奇怪而不幸的行为。

如果你按照步骤操作,把测试放在 IntListmain 的同一个文件中。Zig 的测试通常写在同一个文件中,经常在它们测试的代码附近。当使用 zig test learning.zig 运行测试时,我们会得到了一个令人惊喜的失败:

Test [1/1] test.IntList: add... [gpa] (err): memory address 0x101154000 leaked:
+/code/zig/learning.zig:26:32: 0x100f707b7 in init (test)
+   .items = try allocator.alloc(i64, 2),
+                               ^
+/code/zig/learning.zig:55:29: 0x100f711df in test.IntList: add (test)
+ var list = try IntList.init(testing.allocator);
+
+... MORE STACK INFO ...
+
+[gpa] (err): memory address 0x101184000 leaked:
+/code/test/learning.zig:40:41: 0x100f70c73 in add (test)
+   var larger = try self.allocator.alloc(i64, len * 2);
+                                        ^
+/code/test/learning.zig:59:15: 0x100f7130f in test.IntList: add (test)
+  try list.add(@intCast(i+10));
+
+

此处有多个内存泄漏。幸运的是,测试分配器准确地告诉我们泄漏的内存是在哪里分配的。你现在能发现泄漏了吗?如果没有,请记住,通常情况下,每个 alloc 都应该有一个相应的 free。我们的代码在 deinit 中调用 free 一次。然而在 initalloc 被调用一次,每次调用 add 并需要更多空间时也会调用 alloc。每次我们 alloc 更多空间时,都需要 free 之前的 self.items

// 现有的代码
+var larger = try self.allocator.alloc(i64, len * 2);
+@memcpy(larger[0..len], self.items);
+
+// 添加的代码
+// 释放先前分配的内存
+self.allocator.free(self.items);
+
+

items复制到我们的 larger 切片中后, 添加最后一行free可以解决泄漏的问题。如果运行 zig test learning.zig,便不会再有错误。

ArenaAllocator

通用分配器(GeneralPurposeAllocator)是一个合理的默认设置,因为它在所有可能的情况下都能很好地工作。但在程序中,你可能会遇到一些固定场景,使用更专业的分配器可能会更合适。其中一个例子就是需要在处理完成后丢弃的短期状态。解析器(Parser)通常就有这样的需求。一个 parse 函数的基本轮廓可能是这样的

fn parse(allocator: Allocator, input: []const u8) !Something {
+	const state = State{
+		.buf = try allocator.alloc(u8, 512),
+		.nesting = try allocator.alloc(NestType, 10),
+	};
+	defer allocator.free(state.buf);
+	defer allocator.free(state.nesting);
+
+	return parseInternal(allocator, state, input);
+}
+
+

虽然这并不难管理,但 parseInternal 内可能还会申请临时内存,当然这些内存也需要释放。作为替代方案,我们可以创建一个 ArenaAllocator,一次性释放所有分配:

fn parse(allocator: Allocator, input: []const u8) !Something {
+	// create an ArenaAllocator from the supplied allocator
+	var arena = std.heap.ArenaAllocator.init(allocator);
+
+	// this will free anything created from this arena
+	defer arena.deinit();
+
+	// create an std.mem.Allocator from the arena, this will be
+	// the allocator we'll use internally
+	const aa = arena.allocator();
+
+	const state = State{
+		// we're using aa here!
+		.buf = try aa.alloc(u8, 512),
+
+		// we're using aa here!
+		.nesting = try aa.alloc(NestType, 10),
+	};
+
+	// we're passing aa here, so any we're guaranteed that
+	// any other allocation will be in our arena
+	return parseInternal(aa, state, input);
+}
+
+

ArenaAllocator 接收一个子分配器(在本例中是传入 init 的分配器),然后创建一个新的 std.mem.Allocator。当使用这个新的分配器分配或创建内存时,我们不需要调用 free 或 destroy。当我们调用 arena.deinit 时,会一次性释放所有该分配器申请的内存。事实上,ArenaAllocatorfreedestroy 什么也不做。

必须谨慎使用 ArenaAllocator。由于无法释放单个分配,因此需要确保 ArenaAllocatordeinit 会在合理的内存增长范围内被调用。有趣的是,这种知识可以是内部的,也可以是外部的。例如,在上述代码中,由于状态生命周期的细节属于内部事务,因此在解析器中利用 ArenaAllocator 是合理的。

ArenaAllocator这样的具有一次性释放所有申请内存的分配器,会破坏每一次 alloc 都应该有相应 free 的规则。不过,如果你收到的是一个 std.mem.Allocator,就不应对其底层实现做任何假设。

我们的 IntList 却不是这样。它可以用来存储 10 个或 1000 万个值。它的生命周期可以以毫秒为单位,也可以以周为单位。它无法决定使用哪种类型的分配器。使用 IntList 的代码才有这种知识。最初,我们是这样管理 IntList 的:

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+const allocator = gpa.allocator();
+
+var list = try IntList.init(allocator);
+defer list.deinit();
+
+

我们可以选择 ArenaAllocator 替代:

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+const allocator = gpa.allocator();
+
+var arena = std.heap.ArenaAllocator.init(allocator);
+defer arena.deinit();
+const aa = arena.allocator();
+
+var list = try IntList.init(aa);
+
+// 说实话,我很纠结是否应该调用 list.deinit。
+// 从技术上讲,我们不必这样做,因为我们在上面调用了 defer arena.deinit()。
+
+defer list.deinit();
+
+...
+
+

由于 IntList 接受的参数是 std.mem.Allocator, 因此我们不需要做什么改变。如果 IntList内部创建了自己的 ArenaAllocator,那也是可行的。允许在ArenaAllocator内部创建ArenaAllocator

最后举个简单的例子,我上面提到的 HTTP 服务器在响应中暴露了一个 ArenaAllocator。一旦发送了响应,它就会被清空。由于ArenaAllocator的生命周期可以预测(从请求开始到请求结束),因此它是一种高效的选择。就性能和易用性而言,它都是高效的。

固定缓冲区分配器 FixedBufferAllocator

我们要讨论的最后一个分配器是 std.heap.FixedBufferAllocator,它可以从我们提供的缓冲区(即 []u8)中分配内存。这种分配器有两大好处。首先,由于所有可能使用的内存都是预先创建的,因此速度很快。其次,它自然而然地限制了可分配内存的数量。这一硬性限制也可以看作是一个缺点。另一个缺点是,freedestroy 只对最后分配/创建的项目有效(想想堆栈)。调用释放非最后分配的内存是安全的,但不会有任何作用。

译者注:这不是覆盖的问题。FixedBufferAllocator 会按照栈的方式进行内存分配和释放。你可以分配新的内存块,但只能按照后进先出(LIFO)的顺序释放它们。

const std = @import("std");
+
+pub fn main() !void {
+	var buf: [150]u8 = undefined;
+	var fa = std.heap.FixedBufferAllocator.init(&buf);
+
+	// this will free all memory allocate with this allocator
+	defer fa.reset();
+
+	const allocator = fa.allocator();
+
+	const json = try std.json.stringifyAlloc(allocator, .{
+		.this_is = "an anonymous struct",
+		.above = true,
+		.last_param = "are options",
+	}, .{.whitespace = .indent_2});
+
+	// We can free this allocation, but since we know that our allocator is
+	// a FixedBufferAllocator, we can rely on the above `defer fa.reset()`
+	defer allocator.free(json);
+
+	std.debug.print("{s}\n", .{json});
+}
+
+

输出内容:

{
+  "this_is": "an anonymous struct",
+  "above": true,
+  "last_param": "are options"
+}
+
+

但如果将 buf 更改为 [120]u8,将得到一个内存不足的错误。

固定缓冲区分配器(FixedBufferAllocators)的常见模式是 reset 并重复使用,竞技场分配器(ArenaAllocators)也是如此。这将释放所有先前的分配,并允许重新使用分配器。


由于没有默认的分配器,Zig 在分配方面既透明又灵活。std.mem.Allocator接口非常强大,它允许专门的分配器封装更通用的分配器,正如我们在ArenaAllocator中看到的那样。

更广泛地说,我们希望堆分配的强大功能和相关责任是显而易见的。对于大多数程序来说,分配任意大小、任意生命周期的内存的能力是必不可少的。

然而,由于动态内存带来的复杂性,你应该注意寻找替代方案。例如,上面我们使用了 std.fmt.allocPrint,但标准库中还有一个 std.fmt.bufPrint。后者使用的是缓冲区而不是分配器:

const std = @import("std");
+
+pub fn main() !void {
+	const name = "Leto";
+
+	var buf: [100]u8 = undefined;
+	const greeting = try std.fmt.bufPrint(&buf, "Hello {s}", .{name});
+
+	std.debug.print("{s}\n", .{greeting});
+}
+
+

该 API 将内存管理的负担转移给了调用者。如果名称较长或 buf 较小,bufPrint 可能会返回 NoSpaceLeft 的错误。但在很多情况下,应用程序都有已知的限制,例如名称的最大长度。在这种情况下,bufPrint 更安全、更快速。

动态分配的另一个可行替代方案是将数据流传输到 std.io.Writer。与我们的 Allocator 一样,Writer 也是被许多具体类型实现的接口。上面,我们使用 stringifyAlloc 将 JSON 序列化为动态分配的字符串。我们本可以使用 stringify 将其写入到一个 Writer 中:

const std = @import("std");
+
+pub fn main() !void {
+	const out = std.io.getStdOut();
+
+	try std.json.stringify(.{
+		.this_is = "an anonymous struct",
+		.above = true,
+		.last_param = "are options",
+	}, .{.whitespace = .indent_2}, out.writer());
+}
+
+

Allocator通常是函数的第一个参数,而 Writer通常是最后一个参数。ಠ_ಠ

在很多情况下,用 std.io.BufferedWriter 封装我们的 Writer 会大大提高性能。

我们的目标并不是消除所有动态分配。这行不通,因为这些替代方案只有在特定情况下才有意义。但现在你有了很多选择。从堆栈到通用分配器,以及所有介于两者之间的东西,比如静态缓冲区、流式 Writer 和专用分配器。

+ + + + \ No newline at end of file diff --git a/public/learn/index.html b/public/learn/index.html new file mode 100644 index 0000000..8dafc24 --- /dev/null +++ b/public/learn/index.html @@ -0,0 +1,64 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + +
+

Zig 语言中文社区

+ +
+ +

Learning Zig 中文翻译

+

Section Contents

牟言

《学习 Zig》系列教程最初由 Karl Seguin 编写,该教程行文流畅,讲述的脉络由浅入深,深入浅出,是入门 Zig 非常不错的选择。因此,Zig 中文社区将其翻译成中文,便于在中文用户内阅读与传播。

初次接触 Zig 的用户可以按序号依次阅读,对于有经验的 Zig 开发者可按需阅读感兴趣的章节。

关于原作者

Karl Seguin 在多个领域有着丰富经验,前微软 MVP,他撰写了大量文章,是多个微软公共新闻组的活跃成员。现居新加坡。他还是以下教程的作者:

可以在 http://openmymind.net 找到他的博客,或者通过 @karlseguin 在 Twitter 上关注他。

翻译原则

技术文档的翻译首要原则是准确,但在准确的前提下如何保证『信、达、雅』?这是个挑战,在翻译本教程时,在某些情况下会根据上下文进行意译,便于中文读者阅读。

最后,感谢翻译者的无私贡献。❤️️

离线阅读

在本仓库的 release 页面会定期将本教程导出为 PDF 格式,读者可按需下载。

<!-- TODO -->
+<!-- 读者也可以使用右侧导航栏中的『[整节打印](_print)』将当前版本教程保存为 PDF 格式。 -->
+
+

其他学习资料

由于 Zig 目前还处于快速迭代,因此最权威的资料无疑是官方的 Zig Language Reference,遇到语言的细节问题,基本都可以在这里找到答案。 其次是社区的一些高质量教程,例如:

+ + + + \ No newline at end of file diff --git a/public/learn/installing-zig/index.html b/public/learn/installing-zig/index.html new file mode 100644 index 0000000..515e9b0 --- /dev/null +++ b/public/learn/installing-zig/index.html @@ -0,0 +1,76 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ + +
+

原文地址:https://www.openmymind.net/learning_zig/#install

Zig 官网的下载页面中包含常见平台的预编译二进制文件。在这个页面上,你可以找到最新开发版本和主要版本的二进制文件。本指南所跟踪的最新版本可在页面顶部找到。

对于我的电脑,我会下载 zig-macos-aarch64-0.12.0-dev.161+6a5463951.tar.xz。你使用的可能是不同的平台或更新的版本。展开压缩包,这里面会有一个名为 zig 的二进制文件,你可以按照自己喜欢的方式,为其设置别名(alias)或添加到你的路径(PATH)中。

现在,你可以运行 zig zenzig version 来测试是否安装正确。

译者注:建议读者使用版本管理工具来安装 Zig,具体可参考:[《Zig 多版本管理》]({{< ref “/post/2023-10-14-zig-version-manager.org” >}})。

git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0
+cat <<'EOF' >> $HOME/.bashrc
+source "$HOME/.asdf/asdf.sh"
+source "$HOME/.asdf/completions/asdf.bash"
+EOF
+
+asdf plugin-add zig https://github.com/zigcc/asdf-zig.git
+
+# 安装最新版
+asdf install zig latest
+asdf global zig latest
+zig version
+
+
+ + + + \ No newline at end of file diff --git a/public/learn/language-overview-part1/index.html b/public/learn/language-overview-part1/index.html new file mode 100644 index 0000000..727ff48 --- /dev/null +++ b/public/learn/language-overview-part1/index.html @@ -0,0 +1,266 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ + +
+ +

Table of Contents

+
    +
  • 模块引用
  • 代码注释
  • 函数
  • 结构体
  • 数组和切片
  • 字符串
  • comptime 和 anytype
+
+

原文地址:https://www.openmymind.net/learning_zig/language_overview_1

Zig 是一种强类型编译语言。它支持泛型,具有强大的编译时元编程功能,并且不包含垃圾收集器。许多人认为 Zig 是 C 的现代替代品。因此,该语言的语法与 C 类似,比较明显的就是以分号结尾的语句和以花括号分隔的块。

Zig 代码如下所示:

const std = @import("std");
+
+// 如果 `main` 不是 `pub` (public),此代码将无法编译
+pub fn main() void {
+	const user = User{
+		.power = 9001,
+		.name = "Goku",
+	};
+
+	std.debug.print("{s}'s power is {d}\n", .{user.name, user.power});
+}
+
+pub const User = struct {
+	power: u64,
+	name: []const u8,
+};
+
+

如果将上述内容保存到 learning.zig 文件,并运行 zig run learning.zig,会得到以下输出:Goku's power is 9001

这是一个简单的示例,即使你是第一次看到 Zig,大概率能够看懂这段代码。尽管如此,下面的内容我们还是来逐行分析它。

请参阅安装 Zig 部分,以便快速启动并运行它。

模块引用

很少有程序是在没有标准库或外部库的情况下以单个文件编写的。我们的第一个程序也不例外,它使用 Zig 的标准库来进行打印输出。 Zig 的模块系统非常简单,只依赖于 @import 函数和 pub 关键字(使代码可以在当前文件外部访问)。

@ 开头的函数是内置函数。它们是由编译器提供的,而不是标准库提供的。

我们通过指定模块名称来引用它。 Zig 的标准库以 std 作为模块名。要引用特定文件,需要使用相对路径。例如,将 User 结构移动到它自己的文件中,比如 models/user.zig

// models/user.zig
+pub const User = struct {
+	power: u64,
+	name: []const u8,
+};
+
+

在这种情况下,可以用如下方式引用它:

// main.zig
+const User = @import("models/user.zig").User;
+
+

如果我们的 User 结构未标记为 pub 我们会收到以下错误:'User' is not marked 'pub'

models/user.zig 可以导出不止一项内容。例如,再导出一个常量:

// models/user.zig
+pub const MAX_POWER = 100_000;
+
+pub const User = struct {
+	power: u64,
+	name: []const u8,
+};
+
+

这时,可以这样导入两者:

const user = @import("models/user.zig");
+const User = user.User;
+const MAX_POWER = user.MAX_POWER;
+
+

此时,你可能会有更多的困惑。在上面的代码片段中,user 是什么?我们还没有看到它,如果使用 var 来代替 const 会有什么不同呢?或者你可能想知道如何使用第三方库。这些都是好问题,但要回答这些问题,需要掌握更多 Zig 的知识点。因此,我们现在只需要掌握以下内容:

  • 如何导入 Zig 标准库
  • 如何导入其他文件
  • 如何导出变量、函数定义

代码注释

下面这行 Zig 代码是一个注释:

// 如果 `main` 不是 `pub` (public),此代码将无法编译
+
+

Zig 没有像 C 语言中类似 /* ... */ 的多行注释。

基于注释的文档自动生成功能正在试验中。如果你看过 Zig 的标准库文档,你就会看到它的实际应用。//! 被称为顶级文档注释,可以放在文件的顶部。三斜线注释 (///) 被称为文档注释,可以放在特定位置,如声明之前。如果在错误的地方使用这两种文档注释,编译器都会出错。

函数

下面这行 Zig 代码是程序的入口函数 main

pub fn main() void
+
+

每个可执行文件都需要一个名为 main 的函数:它是程序的入口点。如果我们将 main 重命名为其他名字,例如 doIt ,并尝试运行 zig run learning.zig ,我们会得到下面的错误:'learning' has no member named 'main'

忽略 main 作为程序入口的特殊作用,它只是一个非常基本的函数:不带参数,不返回任何东西(void)。下面的函数会稍微有趣一些:

const std = @import("std");
+
+pub fn main() void {
+	const sum = add(8999, 2);
+	std.debug.print("8999 + 2 = {d}\n", .{sum});
+}
+
+fn add(a: i64, b: i64) i64 {
+	return a + b;
+}
+
+

C 和 C++ 程序员会注意到 Zig 不需要提前声明,即在定义之前就可以调用 add 函数。

接下来要注意的是 i64 类型:64 位有符号整数。其他一些数字类型有: u8i8u16i16u32i32u47i47u64i64f32f64

包含 u47i47 并不是为了测试你是否还清醒; Zig 支持任意位宽度的整数。虽然你可能不会经常使用这些,但它们可以派上用场。经常使用的一种类型是 usize,它是一个无符号指针大小的整数,通常是表示某事物长度、大小的类型。

除了 f32f64 之外,Zig 还支持 f16f80f128 浮点类型。

虽然没有充分的理由这样做,但如果我们将 add 的实现更改为:

fn add(a: i64, b: i64) i64 {
+	a += b;
+	return a;
+}
+
+

a += b 这一行会报下面的错误:不能给常量赋值。这是一个重要的教训,我们稍后将更详细地回顾:函数参数是常量。

为了提高可读性,Zig 中不支持函数重载(用不同的参数类型或参数个数定义的同名函数)。暂时来说,以上就是我们需要了解的有关函数的全部内容。

结构体

下面这行代码创建了一个 User 结构体:

pub const User = struct {
+	power: u64,
+	name: []const u8,
+};
+
+

由于我们的程序是单个文件,因此 User 仅在定义它的文件中使用,因此我们不需要将其设为 pub 。但这样一来,我们就看不到如何将声明暴露给其他文件了。

结构字段以逗号终止,并且可以指定默认值:

pub const User = struct {
+	power: u64 = 0,
+	name: []const u8,
+};
+
+

当我们创建一个结构体时,必须对每个字段赋值。例如,在一开始的定义中 power 没有默认值,因此下面这行代码将报错:missing struct field: power

const user = User{.name = "Goku"};
+
+

但是,使用默认值定义后,上面的代码可以正常编译。

结构体可以有方法,也可以包含声明(包括其他结构),甚至可能包含零个字段,此时的作用更像是命名空间。

pub const User = struct {
+	power: u64 = 0,
+	name: []const u8,
+
+	pub const SUPER_POWER = 9000;
+
+	pub fn diagnose(user: User) void {
+		if (user.power >= SUPER_POWER) {
+			std.debug.print("it's over {d}!!!", .{SUPER_POWER});
+		}
+	}
+};
+
+

方法只是普通函数,只是说可以用 struct.method() 方式调用。以下两种方法等价:

// 调用 user 的 diagnose
+user.diagnose();
+
+// 上面代码等价于:
+User.diagnose(user);
+
+

大多数时候你将使用struct.method()语法,但方法作为普通函数的语法糖在某些场景下可以派上用场。

if 语句是我们看到的第一个控制流。这很简单,对吧?我们将在下一部分中更详细地探讨这一点。

diagnose 在定义 User 类型中,接受 User 作为其第一个参数。因此,我们可以使用struct.method() 的语法来调用它。但结构内的函数不必遵循这种模式。一个常见的例子是用于结构体初始化的 init 函数:

pub const User = struct {
+	power: u64 = 0,
+	name: []const u8,
+
+	pub fn init(name: []const u8, power: u64) User {
+		return User{
+			.name = name,
+			.power = power,
+		};
+	}
+}
+
+

init 的命名方式仅仅是一种约定,在某些情况下,open 或其他名称可能更有意义。如果你和我一样,不是 C++ 程序员,可能对 .$field = $value, 这种初始化字段的语法感到奇怪,但你很快就会习惯它。

当我们创建 "Goku" 时,我们将 user 变量声明为 const

const user = User{
+	.power = 9001,
+	.name = "Goku",
+};
+
+

这意味着我们无法修改 user 的值。如果要修改变量,应使用 var 声明它。另外,你可能已经注意到 user 的类型是根据赋值对象推导出来的。我们也可以这样明确地声明:

const user: User = User{
+	.power = 9001,
+	.name = "Goku",
+};
+
+

在有些情况下我们必须显式声明变量类型,但大多数时候,去掉显式的类型会让代码可读性更好。类型推导也可以这么使用。下面这段代码和上面的两个片段是等价的:

const user: User = .{
+	.power = 9001,
+	.name = "Goku",
+};
+
+

不过这种用法并不常见。比较常见的一种情况是从函数返回结构体时会用到。这里的类型可以从函数的返回类型中推断出来。我们的 init 函数可能会这样写:

pub fn init(name: []const u8, power: u64) User {
+	// instead of return User{...}
+	return .{
+		.name = name,
+		.power = power,
+	};
+}
+
+

就像我们迄今为止已经探索过的大多数东西一样,今后在讨论 Zig 语言的其他部分时,我们会再次讨论结构体。不过,在大多数情况下,它们都是简单明了的。

数组和切片

我们可以略过代码的最后一行,但鉴于我们的代码片段包含两个字符串 "Goku"{s}'s power is {d}\n,你可能会对 Zig 中的字符串感到好奇。为了更好地理解字符串,我们先来了解一下数组和切片。

数组的大小是固定的,其长度在编译时已知。长度是类型的一部分,因此 4 个有符号整数的数组 [4]i32 与 5 个有符号整数的数组 [5]i32 是不同的类型。

数组长度可以从初始化中推断出来。在以下代码中,所有三个变量的类型均为 [5]i32

const a = [5]i32{1, 2, 3, 4, 5};
+
+// 我们已经在结构体中使用过 .{...} 语法,
+// 它也适用于数组
+
+const b: [5]i32 = .{1, 2, 3, 4, 5};
+
+// 使用 _ 让编译器推导长度
+const c = [_]i32{1, 2, 3, 4, 5};
+
+

另一方面,切片是指向数组的指针,外加一个在运行时确定的长度。我们将在后面的部分中讨论指针,但你可以将切片视为数组的视图。

如果你熟悉 Go,你可能已经注意到 Zig 中的切片有点不同:没有容量,只有指针和长度。

const a = [_]i32{1, 2, 3, 4, 5};
+const b = a[1..4];
+
+

在上述代码中, b 是一个长度为 3 的切片,并且是一个指向 a 的指针。但是因为我们使用编译时已知的值来对数组进行切片(即 14)所以长度 3 在编译时也是已知。 Zig 编译器能够分析出来这些信息,因此 b 不是一个切片,而是一个指向长度为 3 的整数数组的指针。具体来说,它的类型是 *const [3]i32。所以这个切片的示例被 Zig 编译器的强大推导能力挫败了。

在实际代码中,切片的使用可能会多于数组。无论好坏,程序的运行时信息往往多于编译时信息。不过,在下面这个例子中,我们必须欺骗 Zig 编译器才能得到我们想要的示例:

const a = [_]i32{1, 2, 3, 4, 5};
+var end: usize = 3;
+end += 1;
+const b = a[1..end];
+
+

b 现在是一个切片了。具体来说,它的类型是 []const i32。你可以看到,切片的长度并不是类型的一部分,因为长度是运行时属性,而类型总是在编译时就完全已知。在创建切片时,我们可以省略上界,创建一个到要切分的对象(数组或切片)末尾的切片,例如 const c = b[2..]

如果我们将 end 声明为 const 那么它将成为编译时已知值,这将导致 b 是一个指向数组的指针,而不是切片。我觉得这有点令人困惑,但它并不是经常出现的东西,而且也不太难掌握。我很想在这一点上跳过它,但无法找到一种诚实的方法来避免这个细节。

学习 Zig 让我了解到,类型具有很强的描述性。它不仅仅是一个整数或布尔值,甚至是一个有符号的 32 位整数数组。类型还包含其他重要信息。我们已经讨论过长度是数组类型的一部分,许多示例也说明了可变性(const-ness)也是数组类型的一部分。例如,在上一个示例中,b 的类型是 []const i32。你可以通过下面的代码来验证这一点:

const std = @import("std");
+
+pub fn main() void {
+	const a = [_]i32{1, 2, 3, 4, 5};
+	var end: usize = 3;
+	end += 1;
+	const b = a[1..end];
+	std.debug.print("{any}", .{@TypeOf(b)});
+}
+
+

如果我们尝试写入 b ,例如 b[2] = 5 ,我们会收到编译时错误:cannot assign to constant.。这就是因为 b 类型是 const 导致。

为了解决这个问题,你可能会想要进行以下更改:

// 将 const 替换为 var
+var b = a[1..end];
+
+

但你会得到同样的错误,为什么?作为提示,b 的类型是什么,或者更通俗地说,b 是什么?切片是指向数组(部分)的长度和指针。切片的类型总是从它所切分的对象派生出来的。无论 b 是否声明为 const,都是一个 [5]const i32 的切片,因此 b 必须是 []const i32 类型。如果我们想写入 b,就需要将 aconst 变为 var

const std = @import("std");
+
+pub fn main() void {
+	var a = [_]i32{1, 2, 3, 4, 5};
+	var end: usize = 3;
+	end += 1;
+	const b = a[1..end];
+	b[2] = 99;
+}
+
+

这是有效的,因为我们的切片不再是 []const i32 而是 []i32 。你可能想知道为什么当 b 仍然是 const 时,这段代码可以执行。这时因为 b 的可变性是指 b 本身,而不是 b 指向的数据。好吧,我不确定这是一个很好的解释,但对我来说,这段代码突出了差异:

const std = @import("std");
+
+pub fn main() void {
+	var a = [_]i32{1, 2, 3, 4, 5};
+	var end: usize = 3;
+	end += 1;
+	const b = a[1..end];
+	b = b[1..];
+}
+
+

上述代码不会编译;正如编译器告诉我们的,我们不能给常量赋值。但如果将代码改成 var b = a[1..end] ,那么代码就是正确的了,因为 b 本身不再是常量。

在了解 Zig 语言的其他方面(尤其是字符串)的同时,我们还将发现更多有关数组和切片的知识。

字符串

我希望我能说,Zig 里有字符串类型,而且非常棒。遗憾的是,它没有。最简单来说,字符串是字节(u8)的序列(即数组或切片)。实际上,我们可以从 name 字段的定义中看到这一点:name: []const u8.

按照惯例,这类字符串大多数都是用 UTF-8 编码,因为 Zig 源代码本身就是 UTF-8 编码的。但这并不是强制的,而且代表 ASCII 或 UTF-8 字符串的 []const u8 与代表任意二进制数据的 []const u8 实际上并没有什么区别。怎么可能有区别呢,它们是相同的类型。

根据我们所学的数组和切片知识,你可以正确地猜测 []const u8 是对常量字节数组的切片(其中字节是一个无符号 8 位整数)。但我们的代码中没有任何地方对数组进行切分,甚至没有数组,对吧?我们所做的只是将 "Goku" 赋值给 user.name。这是怎么做到的呢?

你在源代码中看到的字符串字面量有一个编译时已知的长度。编译器知道 "Goku" 的长度是 4,所以你会认为 "Goku" 最好用数组来表示,比如 [4]const u8。但是字符串字面形式有几个特殊的属性。它们被存储在二进制文件的一个特殊位置,并且会去重。因此,指向字符串字面量的变量将是指向这个特殊位置的指针。也就是说,"Goku" 的类型更接近于 *const [4]u8,是一个指向 4 常量字节数组的指针。

还有更多。字符串字面量以空值结束。也就是说,它们的末尾总是有一个 \0。在内存中,"Goku" 实际上是这样的:{'G', 'o', 'k', 'u', 0},所以你可能会认为它的类型是 *const [5]u8。但这样做充其量只是模棱两可,更糟糕的是会带来危险(你可能会覆盖空结束符)。相反,Zig 有一种独特的语法来表示以空结尾的数组。"Goku"的类型是 *const[4:0]u8,即 4 字节以空结尾的数组指针。当我们讨论字符串时,我们关注的是以空结尾的字节数组(因为在 C 语言中字符串通常就是这样表示的),语法更通用:[LENGTH:SENTINEL],其中 SENTINEL 是数组末尾的特殊值。因此,虽然我想不出为什么需要它,但下面的语法是完全正确的:

const std = @import("std");
+
+pub fn main() void {
+	// an array of 3 booleans with false as the sentinel value
+	const a = [3:false]bool{false, true, false};
+
+	// This line is more advanced, and is not going to get explained!
+	std.debug.print("{any}\n", .{std.mem.asBytes(&a).*});
+}
+
+

上面代码会输出:{ 0, 1, 0, 0}

我一直在犹豫是否要加入这个示例,因为最后一行非常高级,我不打算解释它。从另一个角度看,如果你愿意的话,这也是一个可以运行的示例,你可以用它来更好地研究我们到目前为止讨论过的一些问题。

如果我的解释还可以接受,那么你可能还有一点不清楚。如果 "Goku" 是一个 *const [4:0]u8 ,那么我们为什么能将它赋值给一个 []const u8 值呢?答案很简单:Zig 会自动进行类型转化。它会在几种不同的类型之间进行类型转化,但最明显的是字符串。这意味着,如果函数有一个 []const u8 参数,或者结构体有一个 []const u8 字段,就可以使用字符串字面形式。由于以空结尾的字符串是数组,而且数组的长度是已知的,因此这种转化代价比较低,即不需要遍历字符串来查找空结束符。

因此,在谈论字符串时,我们通常指的是 []const u8。必要时,我们会明确说明一个以空结尾的字符串,它可以被自动转化为一个 []const u8。但请记住,[]const u8 也用于表示任意二进制数据,因此,Zig 并不像高级编程语言那样有字符串的概念。此外,Zig 的标准库只有一个非常基本的 unicode 模块。

当然,在实际程序中,大多数字符串(以及更通用的数组)在编译时都是未知的。最典型的例子就是用户输入,程序编译时并不知道用户输入。这一点我们将在讨论内存时再次讨论。但简而言之,对于这种在编译时不能确定值的数据(长度当然也就无从得知),我们将在运行时动态分配内存。我们的字符串变量(仍然是 []const u8 类型)将是指向动态分配的内存的切片。

comptime 和 anytype

在我们未解释的最后一行代码中,涉及的知识远比表面看到的多:

std.debug.print("{s}'s power is {d}\n", .{user.name, user.power});
+
+

我们只是略微浏览了一下,但它确实提供了一个机会来强调 Zig 的一些更强大的功能。即使你还没有掌握,至少也应该了解这些功能。

首先是 Zig 的编译时执行(compile-time execution)概念。编译时执行是 Zig 元编程功能的核心,顾名思义,就是在编译时而不是运行时运行代码。在本指南中,我们将对编译时可能实现的功能进行浅显介绍,更多高级功能读者可以参考其他资料。

你可能想知道上面这行代码中需要编译时执行的是什么。print 函数的定义要求我们的第一个参数(字符串格式)是编译时已知的:

// 注意变量"fmt"前的"comptime"
+pub fn print(comptime fmt: []const u8, args: anytype) void {
+
+

原因是 print 会进行额外的编译时检查,而这在大多数其他语言中是不会出现的。什么样的检查呢?假设你把格式改为 it's over {d}/n,但保留了两个参数。你会得到一个编译时错误:unused argument in 'it's over {d}'。它还会进行类型检查:将格式字符串改为{s}'s power is {s}\n,你会这个错误invalid format string 's' for type 'u64'。如果在编译时不知道字符串的格式,就不可能在编译时进行这些检查。因此,需要一个编译时已知的值。

comptime 会对编码产生直接影响的地方是整数和浮点字面的默认类型,即特殊的 comptime_intcomptime_float。这行代码是无效的:var i = 0comptime代码只能使用编译时已知的数据,对于整数和浮点数,这类数据由特殊的 comptime_intcomptime_float 类型标识。这种类型的值可以在编译时执行。但你可能不会把大部分时间花在编写用于编译时执行的代码上,因此它并不是一个特别有用的默认值。你需要做的是给变量一个显式类型:

var i: usize = 0;
+var j: f64 = 0;
+
+

注意,如果我们使用const,就不会出现这个错误,因为错误的关键在于 comptime_int 必须是常量。

在以后的章节中,我们将在探索泛型时进一步研究 comptime

我们这行代码的另一个特别之处在于奇怪的 .{user.name, user.power},根据上述 print 的定义,我们知道它映射到 anytype 类型的变量。这种类型不应与 Java 的 Object 或 Go 的 any(又名 interface{})混淆。相反,在编译时,Zig 会为传递给它的所有类型专门创建一个单独的 print 函数。

这就引出了一个问题:我们传递给它的是什么?我们以前在让编译器推断结构类型时见过 .{...} 符号。这与此类似:它创建了一个匿名结构字面。请看这段代码

pub fn main() void {
+	std.debug.print("{any}\n", .{@TypeOf(.{.year = 2023, .month = 8})});
+}
+
+

会输出:

struct{comptime year: comptime_int = 2023, comptime month: comptime_int = 8}
+

在这里,我们给匿名结构的字段取名为 yearmonth。在原始代码中,我们没有这样做。在这种情况下,字段名会自动生成 0、1、2 等。虽然它们都是匿名结构字面形式的示例,但没有字段名称的结构通常被称为“元组”(tuple)。print 函数希望接收一个元组,并使用字符串格式中的序号位置来获取适当的参数。

Zig 没有函数重载,也没有可变函数(vardiadic,具有任意数量参数的函数)。但它的编译器能根据传入的类型创建专门的函数,包括编译器自己推导和创建的类型。

+ + + + \ No newline at end of file diff --git a/public/learn/language-overview-part2/index.html b/public/learn/language-overview-part2/index.html new file mode 100644 index 0000000..3497f4c --- /dev/null +++ b/public/learn/language-overview-part2/index.html @@ -0,0 +1,368 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ + +
+ +

Table of Contents

+
    +
  • 控制流
  • 枚举
  • 带标签的联合 Tagged Union
  • 可选类型 Optional
  • 未定义的值 Undefined
  • 错误 Errors
+
+

原文地址:https://www.openmymind.net/learning_zig/language_overview_2

本部分继续上一部分的内容:熟悉 Zig 语言。我们将探索 Zig 的控制流和结构以外的类型。通过这两部分的学习,我们将掌握 Zig 语言的大部分语法,这让我们可以继续深入 Zig 语言,同时也为如何使用 std 标准库打下了基础。

控制流

Zig 的控制流很可能是我们所熟悉的,但它与 Zig 语言的其他特性协同工作是我们还没有探索过。我们先简单概述控制流的基本使用,之后在讨论依赖控制流的相关特性时,再来重新回顾。

你会注意到,我们使用 andor 来代替逻辑运算符 &&||。与大多数语言一样,andor 会短路执行,即如果左侧为假,and 的右侧运算符就不会执行;如果左侧为真,or 的右侧就不会执行。在 Zig 中,控制流是通过关键字完成的,因此要使用 andor

此外,比较运算符 == 在切片(如 []const u8,即字符串)间不起作用。在大多数情况下,需要使用 std.mem.eql(u8,str1,str2),它将比较两个片段的长度和字节数。

Zig 中,ifelse ifelse 也很常见:

// std.mem.eql 将逐字节进行比较,对于字符串来说它是大小写敏感的。
+if (std.mem.eql(u8, method, "GET") or std.mem.eql(u8, method, "HEAD")) {
+	// 处理 GET 请求
+} else if (std.mem.eql(u8, method, "POST")) {
+	// 处理 POST 请求
+} else {
+	// ...
+}
+
+

std.mem.eql 的第一个参数是一个类型,这里是 u8。这是我们看到的第一个泛型函数。我们将在后面的部分进一步探讨。

上述示例比较的是 ASCII 字符串,不区分大小写可能更合适,这时 std.ascii.eqlIgnoreCase(str1, str2) 可能是更好的选择。

虽然没有三元运算符,但可以使用 if/else 来代替:

const super = if (power > 9000) true else false;
+
+

switch 语句类似于if/else if/else,但具有穷举的优点。也就是说,如果没有涵盖所有情况,编译时就会出错。下面这段代码将无法编译:

fn anniversaryName(years_married: u16) []const u8 {
+	switch (years_married) {
+		1 => return "paper",
+		2 => return "cotton",
+		3 => return "leather",
+		4 => return "flower",
+		5 => return "wood",
+		6 => return "sugar",
+	}
+}
+
+

编译时会报错:switch 必须处理所有的可能性。由于我们的 years_married 是一个 16 位整数,这是否意味着我们需要处理所有 64K 中情况?是的,不过我们可以使用 else 来代替:

// ...
+6 => return "sugar",
+else => return "no more gifts for you",
+
+

在进行匹配时,我们可以合并多个 case 或使用范围;在进行处理时,可以使用代码块来处理复杂的情况:

fn arrivalTimeDesc(minutes: u16, is_late: bool) []const u8 {
+	switch (minutes) {
+		0 => return "arrived",
+		1, 2 => return "soon",
+		3...5 => return "no more than 5 minutes",
+		else => {
+			if (!is_late) {
+				return "sorry, it'll be a while";
+			}
+			// todo, something is very wrong
+			return "never";
+		},
+	}
+}
+
+

虽然 switch 在很多情况下都很有用,但在处理枚举时,它穷举的性质才真正发挥了作用,我们很快就会谈到枚举。

Zig 的 for 循环用于遍历数组、切片和范围。例如,我们可以这样写:

fn contains(haystack: []const u32, needle: u32) bool {
+	for (haystack) |value| {
+		if (needle == value) {
+			return true;
+		}
+	}
+	return false;
+}
+
+

for 循环也可以同时处理多个序列,只要这些序列的长度相同。上面我们使用了 std.mem.eql 函数,下面是其大致实现:

pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
+	// if they aren't the same length, they can't be equal
+	if (a.len != b.len) return false;
+
+	for (a, b) |a_elem, b_elem| {
+		if (a_elem != b_elem) return false;
+	}
+
+	return true;
+}
+
+

一开始的 if 检查不仅是一个很好的性能优化,还是一个必要的防护措施。如果我们去掉它,并传递不同长度的参数,就会出现运行时 panicfor 在作用于多个序列上时,要求其长度相等。

for 循环也可以遍历范围,例如:

for (0..10) |i| {
+	std.debug.print("{d}\n", .{i});
+}
+
+

switch 中,范围使用了三个点,即 3...6,而这个示例中,范围使用了两个点,即 0..10。这是因为在 switch 中,范围的两端都是闭区间,而 for 则是左闭右开。

与一个(或多个)序列组合使用时,它的作用就真正体现出来了:

fn indexOf(haystack: []const u32, needle: u32) ?usize {
+	for (haystack, 0..) |value, i| {
+		if (needle == value) {
+			return i;
+		}
+	}
+	return null;
+}
+
+

这是对可空类型的初步了解。

范围的末端由 haystack 的长度推断,不过我们也可以写出 0..haystack.len,但这没有必要。for 循环不支持常见的 init; compare; step 风格,对于这种情况,可以使用 while

因为 while 比较简单,形式如下:while (condition) { },这有利于更好地控制迭代。例如,在计算字符串中转义序列的数量时,我们需要将迭代器递增 2 以避免重复计算 \\

var escape_count: usize = 0;
+{
+	var i: usize = 0;
+	// 反斜杠用作转义字符,因此我们需要用一个反斜杠来转义它。
+	while (i < src.len) {
+		if (src[i] == '\\') {
+			i += 2;
+			escape_count += 1;
+		} else {
+			i += 1;
+		}
+	}
+}
+
+

我们在临时变量 iwhile 循环周围添加了一个显式的代码块。这缩小了 i 的作用范围。这样的代码块可能会很有用,尽管在这个例子中可能有些过度。不过,上述例子已经是 Zig 中最接近传统的 for(init; compare; step) 循环的写法了。

while 可以包含 else 子句,当条件为假时执行 else 子句。它还可以接受在每次迭代后要执行的语句。多个语句可以用 ; 分隔。在 for 支持遍历多个序列之前,这一功能很常用。上述语句可写成

var i: usize = 0;
+var escape_count: usize = 0;
+
+// 改写后的
+while (i < src.len) : (i += 1) {
+	if (src[i] == '\\') {
+		// +1 here, and +1 above == +2
+		// 这里 +1,上面也 +1,相当于 +2
+		i += 1;
+		escape_count += 1;
+	}
+}
+
+
+

Zig 也支持 breakcontinue 关键字,用于跳出最内层循环或跳转到下一次迭代。

代码块可以附带标签(label),breakcontinue 可以作用在特定标签上。举例说明:

outer: for (1..10) |i| {
+	for (i..10) |j| {
+		if (i * j > (i+i + j+j)) continue :outer;
+		std.debug.print("{d} + {d} >= {d} * {d}\n", .{i+i, j+j, i, j});
+	}
+}
+
+

break 还有另一个有趣的行为,即从代码块中返回值:

const personality_analysis = blk: {
+	if (tea_vote > coffee_vote) break :blk "sane";
+	if (tea_vote == coffee_vote) break :blk "whatever";
+	if (tea_vote < coffee_vote) break :blk "dangerous";
+};
+
+

像这样有返回值的的块,必须以分号结束。

稍后,当我们讨论带标签的联合(tagged union)、错误联合(error unions)和可选类型(Optional)时,我们将看到控制流如何与它们联合使用。

枚举

枚举是带有标签的整数常量。它们的定义很像结构体:

// 可以是 "pub" 的
+const Status = enum {
+	ok,
+	bad,
+	unknown,
+};
+
+

与结构体一样,枚举可以包含其他定义,包括函数,这些函数可以选择性地将枚举作为第一个参数:

const Stage = enum {
+	validate,
+	awaiting_confirmation,
+	confirmed,
+	err,
+
+	fn isComplete(self: Stage) bool {
+		return self == .confirmed or self == .err;
+	}
+};
+
+

如果需要枚举的字符串表示,可以使用内置的 @tagName(enum) 函数。

回想一下,结构类型可以使用 .{...} 符号根据其赋值或返回类型来推断。在上面,我们看到枚举类型是根据与 self 的比较推导出来的,而 self 的类型是 Stage。我们本可以明确地写成:return self == Stage.confirmedself == Stage.err。但是,在处理枚举时,你经常会看到通过 .$value 这种省略具体类型的情况。这被称为枚举字面量

switch 的穷举性质使它能与枚举很好地搭配,因为它能确保你处理了所有可能的情况。不过在使用 switchelse 子句时要小心,因为它会匹配任何新添加的枚举值,而这可能不是我们想要的行为。

带标签的联合 Tagged Union

联合定义了一个值可以具有的一系列类型。例如,这个 Number 可以是整数、浮点数或 nan(非数字):

const std = @import("std");
+
+pub fn main() void {
+	const n = Number{.int = 32};
+	std.debug.print("{d}\n", .{n.int});
+}
+
+const Number = union {
+	int: i64,
+	float: f64,
+	nan: void,
+};
+
+

一个联合一次只能设置一个字段;试图访问一个未设置的字段是错误的。既然我们已经设置了 int 字段,如果我们试图访问 n.float,就会出错。我们的一个字段 nanvoid 类型。我们该如何设置它的值呢?使用 {}

const n = Number{.nan = {}};
+
+

使用联合的一个难题是要知道设置的是哪个字段。这就是带标签的联合发挥作用的地方。带标签的联合将枚举与联合定义在一起,可用于 switch 语句中。请看下面这个例子:

pub fn main() void {
+	const ts = Timestamp{.unix = 1693278411};
+	std.debug.print("{d}\n", .{ts.seconds()});
+}
+
+const TimestampType = enum {
+	unix,
+	datetime,
+};
+
+const Timestamp = union(TimestampType) {
+	unix: i32,
+	datetime: DateTime,
+
+	const DateTime = struct {
+		year: u16,
+		month: u8,
+		day: u8,
+		hour: u8,
+		minute: u8,
+		second: u8,
+	};
+
+	fn seconds(self: Timestamp) u16 {
+		switch (self) {
+			.datetime => |dt| return dt.second,
+			.unix => |ts| {
+				const seconds_since_midnight: i32 = @rem(ts, 86400);
+				return @intCast(@rem(seconds_since_midnight, 60));
+			},
+		}
+	}
+};
+
+

请注意, switch 中的每个分支捕获了字段的类型值。也就是说,dtTimestamp.DateTime 类型,而 tsi32 类型。这也是我们第一次看到嵌套在其他类型中的结构。DateTime 本可以在联合之外定义。我们还看到了两个新的内置函数:@rem 用于获取余数,@intCast 用于将结果转换为 u16@intCast 从返回值类型中推断出我们需要 u16)。

从上面的示例中我们可以看出,带标签的联合的使用有点像接口,只要我们提前知道所有可能的实现,我们就能够将其转化带标签的联合这种形式。

最后,带标签的联合中的枚举类型可以自动推导出来。我们可以直接这样做:

const Timestamp = union(enum) {
+	unix: i32,
+	datetime: DateTime,
+
+	...
+
+

这里 Zig 会根据带标签的联合,自动创建一个隐式枚举。

可选类型 Optional

在类型前加上问号 ?,任何值都可以声明为可选类型。可选类型既可以是 null,也可以是已定义类型的值:

var home: ?[]const u8 = null;
+var name: ?[]const u8 = "Leto";
+
+

明确类型的必要性应该很清楚:如果我们只使用 const name = "Leto",那么推导出的类型将是非可选的 []const u8

.?用于访问可选类型后面的值:

std.debug.print("{s}\n", .{name.?});
+
+
+

但如果在 null 上使用 .?,运行时就会 panicif 语句可以安全地取出可选类型背后的值:

if (home) |h| {
+	// h is a []const u8
+	// we have a home value
+} else {
+	// we don't have a home value
+}
+
+

orelse 可用于提取可选类型的值或执行代码。这通常用于指定默认值或从函数中返回:

const h = home orelse "unknown"
+
+// 或直接返回函数
+const h = home orelse return;
+
+

不过,orelse 也可以带一个代码块,用于执行更复杂的逻辑。可选类型还可以与 while 整合,经常用于创建迭代器。我们这里忽略迭代器的细节,但希望这段伪代码能说明问题:

while (rows.next()) |row| {
+	// do something with our row
+}
+
+

未定义的值 Undefined

到目前为止,我们看到的每一个变量都被初始化为一个合理的值。但有时我们在声明变量时并不知道它的值。可选类型是一种选择,但并不总是合理的。在这种情况下,我们可以将变量设置为未定义,让其保持未初始化状态。

通常这样做的一个地方是创建数组,其值将由某个函数来填充:

var pseudo_uuid: [16]u8 = undefined;
+std.crypto.random.bytes(&pseudo_uuid);
+
+

上述代码仍然创建了一个 16 字节的数组,但它的每个元素都没有被赋值。

错误 Errors

Zig 中错误处理功能十分简单、实用。这一切都从错误集(error sets)开始,错误集的使用方式类似于枚举:

// 与第 1 部分中的结构一样,OpenError 也可以标记为 "pub"。
+// 使其可以在其定义的文件之外访问
+const OpenError = error {
+	AccessDenied,
+	NotFound,
+};
+
+

任意函数(包括 main)都可以返回这个错误:

pub fn main() void {
+	return OpenError.AccessDenied;
+}
+
+const OpenError = error {
+	AccessDenied,
+	NotFound,
+};
+
+

如果你尝试运行这个程序,你会得到一个错误:expected type 'void', found 'error{AccessDenied,NotFound}'。这是有道理的:我们定义了返回类型为 voidmain 函数,但我们却返回了另一种东西(很明显,它是一个错误,而不是 void)。要解决这个问题,我们需要更改函数的返回类型。

pub fn main() OpenError!void {
+	return OpenError.AccessDenied;
+}
+
+

这就是所谓的错误联合类型,它表示我们的函数既可以返回 OpenError 错误,也可以返回 void(也就是什么都没有)。到目前为止,我们已经非常明确:我们为函数可能返回的错误创建了一个错误集,并在函数的错误联合类型中使用了该错误集。但是,说到错误,Zig 有一些巧妙的技巧。首先,我们可以让 Zig 通过使用 !return_type 来推导错误集,而不是将 error union 指定为 error_set!return_type。因此,我们可以(也推荐)将我们 main 函数定义为:

pub fn main() !void
+
+
+

其次,Zig 能够为我们隐式创建错误集。我们可以这样做,而不需要提前声明:

pub fn main() !void {
+	return error.AccessDenied;
+}
+
+

完全显式和隐式方法并不完全等同。例如,引用具有隐式错误集的函数时,需要使用特殊的 anyerror 类型。类库开发人员可能会发现显式的好处,比如可以达到代码即文档的效果。不过,我认为隐式错误集和推导错误联合类型都很实用;我在平时编程中,大量使用了这两种方法。

错误联合类型的真正价值在于 Zig 语言提供了 catchtry 来处理它们。返回错误联合类型的函数调用时,可以包含一个 catch 子句。例如,一个 http 服务器库的代码可能如下所示:

action(req, res) catch |err| {
+	if (err == error.BrokenPipe or err == error.ConnectionResetByPeer) {
+		return;
+	} else if (err == error.BodyTooBig) {
+		res.status = 431;
+		res.body = "Request body is too big";
+	} else {
+		res.status = 500;
+		res.body = "Internal Server Error";
+		// todo: log err
+	}
+};
+
+

switch 的版本更符合惯用法:

action(req, res) catch |err| switch (err) {
+	error.BrokenPipe, error.ConnectionResetByPeer) => return,
+	error.BodyTooBig => {
+		res.status = 431;
+		res.body = "Request body is too big";
+	},
+	else => {
+		res.status = 500;
+		res.body = "Internal Server Error";
+	}
+};
+
+

这看起来花哨,但老实说,你在 catch 中最有可能做的事情就是把错误信息给调用者:

action(req, res) catch |err| return err;
+
+

这种模式非常常见,因此 Zig 提供了 try 关键字用于处理这种情况。上述代码的另一种写法如下:

try action(req, res);
+
+

鉴于必须处理错误,这一点尤其有用。多数情况下的做法就是使用 trycatch

Go 开发人员会注意到,tryif err != nil { return err } 的按键次数更少。

大多数情况下,你都会使用 trycatch,但 ifwhile 也支持错误联合类型,这与可选类型很相似。在 while 的情况下,如果条件返回错误,则执行 else 子句。

有一种特殊的 anyerror 类型可以容纳任何错误。虽然我们可以将函数定义为返回 anyerror!TYPE 而不是 !TYPE,但两者并不等同。anyerror 是全局错误集,是程序中所有错误集的超集。因此,在函数签名中使用 anyerror 很可能表示这个函数虽然可以返回错误,而实际上它大概率不会返回错误。 anyerror 主要用在可以是任意错误类型的函数参数或结构体字段中(想象一下日志库)。

函数同时返回可选类型与错误联合类型的情况并不少见。在推导错误集的情况下,形式如下:

// 载入上次保存的游戏
+pub fn loadLast() !?Save {
+	// TODO
+	return null;
+}
+
+

使用此类函数有多种方法,但最简洁的方法是使用 try 来解除错误,然后使用 orelse 来解除可选类型。下面是一个大致的模式:

const std = @import("std");
+
+pub fn main() void {
+	// This is the line you want to focus on
+	const save = (try Save.loadLast()) orelse Save.blank();
+	std.debug.print("{any}\n", .{save});
+}
+
+pub const Save = struct {
+	lives: u8,
+	level: u16,
+
+	pub fn loadLast() !?Save {
+		//todo
+		return null;
+	}
+
+	pub fn blank() Save {
+		return .{
+			.lives = 3,
+			.level = 1,
+		};
+	}
+};
+
+

虽然我们还未涉及 Zig 语言中更高级的功能,但我们在前两部分中看到的是 Zig 语言重要组成部分。它们将作为一个基础,让我们能够探索更复杂的话题,而不用被语法所困扰。

+ + + + \ No newline at end of file diff --git a/public/learn/pointers/index.html b/public/learn/pointers/index.html new file mode 100644 index 0000000..6fcdfd4 --- /dev/null +++ b/public/learn/pointers/index.html @@ -0,0 +1,346 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ + +
+ +

Table of Contents

+
    +
  • 方法
  • 常量函数参数
  • 指向指针的指针
  • 嵌套指针
  • 递归结构
+
+

原文地址:https://www.openmymind.net/learning_zig/pointers

Zig 不包含垃圾回收器。管理内存的重任由开发者负责。这是一项重大责任,因为它直接影响到应用程序的性能、稳定性和安全性。

我们将从指针开始讨论,这本身就是一个重要的话题,同时也是训练我们从面向内存的角度来看待程序数据的开始。如果你已经对指针、堆分配和悬挂指针了如指掌,那么可以跳过本小节和下一小节,直接阅读[堆内存和分配器]({{< ref heap-memory-and-allocator.md >}}),这部分内容与 Zig 更为相关。


下面的代码创建了一个 power 为 100 的用户,然后调用 levelUp 函数将用户的 power 加一。你能猜到它的输出结果吗?

const std = @import("std");
+
+pub fn main() void {
+	var user = User{
+		.id = 1,
+		.power = 100,
+	};
+
+	// this line has been added
+	levelUp(user);
+	std.debug.print("User {d} has power of {d}\n", .{user.id, user.power});
+}
+
+fn levelUp(user: User) void {
+	user.power += 1;
+}
+
+pub const User = struct {
+	id: u64,
+	power: i32,
+};
+
+

这里我设置了一个陷阱,此段代码将无法编译:局部变量从未被修改。这是指 main 函数中的 user 变量。一个从未被修改的变量必须声明为 const。你可能会想:但在 levelUp 函数中我们确实修改了 user,这怎么回事?让我们假设 Zig 编译器弄错了,并试着糊弄它。我们将强制让编译器看到 user 确实被修改了:

const std = @import("std");
+
+pub fn main() void {
+	var user = User{
+		.id = 1,
+		.power = 100,
+	};
+	user.power += 0;
+
+	// 代码的其余部分保持不变。
+
+

现在我们在 levelUp 中遇到了一个错误:不能赋值给常量。我们在第一部分中看到函数参数是常量,因此 user.power += 1 是无效的。为了解决这个错误,我们可以将 levelUp 函数改为

fn levelUp(user: User) void {
+	var u = user;
+	u.power += 1;
+}
+
+

虽然编译成功了,但输出结果却是User 1 has power of 100,而我们代码的目的显然是让 levelUp 将用户的 power 提升到 101。这是怎么回事?

要理解这一点,我们可以将数据与内存联系起来,而变量只是将类型与特定内存位置关联起来的标签。例如,在 main 中,我们创建了一个User。内存中数据的简单可视化表示如下

user -> ------------ (id)
+        |    1     |
+        ------------ (power)
+        |   100    |
+        ------------
+

有两点需要注意:

  1. 我们的user变量指向结构的起点
  2. 字段是按顺序排列的

请记住,我们的user也有一个类型。该类型告诉我们 id 是一个 64 位整数,power 是一个 32 位整数。有了对数据起始位置的引用和类型,编译器就可以将 user.power 转换为:访问位置在结构体第 64 位上的一个 32 位整数。这就是变量的威力,它们可以引用内存,并包含以有意义的方式理解和操作内存所需的类型信息。

默认情况下,Zig 不保证结构的内存布局。它可以按字母顺序、大小升序或插入填充(padding)某些字段。只要它能正确翻译我们的代码,它就可以为所欲为。这种自由度可以实现某些优化。只有在声明 packed struct时,我们才能获得内存布局的有力保证。我们还可以创建一个 extern struct,这样可以保证内存布局与 C 应用程序二进制接口 (ABI) 匹配。尽管如此,我们对user的可视化还是合理而有用的。

下面是一个稍有不同的可视化效果,其中包括内存地址。这些数据的起始内存地址是我想出来的一个随机地址。这是user变量引用的内存地址,也是第一个字段 id 的值所在的位置。由于 id 是一个 64 位整数,需要 8 字节内存。因此,power 必须位于 $start_address + 8 上:

user ->   ------------  (id: 1043368d0)
+          |    1     |
+          ------------  (power: 1043368d8)
+          |   100    |
+          ------------
+

为了验证这一点,我想介绍一下取地址符运算符:&。顾名思义,取地址运算符返回一个变量的地址(它也可以返回一个函数的地址,是不是很神奇?)保留现有的 User 定义,试试下面的代码:

pub fn main() void {
+	const user = User{
+		.id = 1,
+		.power = 100,
+	};
+	std.debug.print("{*}\n{*}\n{*}\n", .{&user, &user.id, &user.power});
+}
+
+

这段代码输出了useruser.id、和user.power的地址。根据平台等差异,可能会得到不同的输出结果,但都会看到useruser.id的地址相同,而user.power的地址偏移量了 8 个字节。输出的结果如下:

learning.User@1043368d0
+u64@1043368d0
+i32@1043368d8
+

取地址运算符返回一个指向值的指针。指向值的指针是一种特殊的类型。类型T的值的地址是*T。因此,如果我们获取 user 的地址,就会得到一个 *User,即一个指向 User 的指针:

pub fn main() void {
+	var user = User{
+		.id = 1,
+		.power = 100,
+	};
+	user.power += 0;
+
+	const user_p = &user;
+	std.debug.print("{any}\n", .{@TypeOf(user_p)});
+}
+
+

我们最初的目标是通过levelUp函数将用户的power值增加 1 。我们已经让代码编译通过,但当我们打印power时,它仍然是原始值。虽然有点跳跃,但让我们修改代码,在 mainlevelUp 中打印 user的地址:

pub fn main() void {
+	var user = User{
+		.id = 1,
+		.power = 100,
+	};
+	user.power += 0;
+
+	// added this
+	std.debug.print("main: {*}\n", .{&user});
+
+	levelUp(user);
+	std.debug.print("User {d} has power of {d}\n", .{user.id, user.power});
+}
+
+fn levelUp(user: User) void {
+	// add this
+	std.debug.print("levelUp: {*}\n", .{&user});
+	var u = user;
+	u.power += 1;
+}
+
+

如果运行这个程序,会得到两个不同的地址。这意味着在 levelUp 中被修改的 usermain 中的user是不同的。这是因为 Zig 传递了一个值的副本。这似乎是一个奇怪的默认行为,但它的好处之一是,函数的调用者可以确保函数不会修改参数(因为它不能)。在很多情况下,有这样的保证是件好事。当然,有时我们希望函数能修改参数,比如 levelUp。为此,我们需要 levelUp 作用于 mainuser,而不是其副本。我们可以通过向函数传递 user的地址来实现这一点:

const std = @import("std");
+
+pub fn main() void {
+	var user = User{
+		.id = 1,
+		.power = 100,
+	};
+
+	// no longer needed
+	// user.power += 1;
+
+	// user -> &user
+	levelUp(&user);
+	std.debug.print("User {d} has power of {d}\n", .{user.id, user.power});
+}
+
+// User -> *User
+fn levelUp(user: *User) void {
+	user.power += 1;
+}
+
+pub const User = struct {
+	id: u64,
+	power: i32,
+};
+
+

我们必须做两处改动。首先是用 user 的地址(即 &user )来调用 levelUp,而不是 user。这意味着我们的函数参数不再是 User,取而代之的是一个 *User,这是我们的第二处改动。

我们不再需要通过 user.power += 0; 来强制修改 user 的那个丑陋的技巧了。最初,我们因为 user 是 var 类型而无法让代码编译,编译器告诉我们它从未被修改。我们以为编译器错了,于是通过强制修改来“糊弄”它。但正如我们现在所知道的,在 levelUp 中被修改的 user 是不同的;编译器是正确的。

现在,代码已按预期运行。虽然在函数参数和内存模型方面仍有许多微妙之处,但我们正在取得进展。现在也许是一个好时机来说明一下,除了特定的语法之外,这些都不是 Zig 所独有的。我们在这里探索的模型是最常见的,有些语言可能只是向开发者隐藏了很多细节,因此也就隐藏了灵活性。

方法

一般来说,我们会把 levelUp 写成 User结构的一个方法:

pub const User = struct {
+	id: u64,
+	power: i32,
+
+	fn levelUp(user: *User) void {
+		user.power += 1;
+	}
+};
+
+

这就引出了一个问题:我们如何调用带有指针参数的方法?也许我们必须这样做:&user.levelUp()?实际上,只需正常调用即可,即 user.levelUp()。Zig 知道该方法需要一个指针,因此会正确地传递值(通过引用传递)。

我最初选择函数是因为它很明确,因此更容易学习。

常量函数参数

我不止一次暗示过,在默认情况下,Zig 会传递一个值的副本(称为 “按值传递”)。很快我们就会发现,实际情况要更微妙一些(提示:嵌套对象的复杂值怎么办?)

即使坚持使用简单类型,事实也是 Zig 可以随心所欲地传递参数,只要它能保证代码的意图不受影响。在我们最初的 levelUp 中,参数是一个User,Zig 可以传递用户的副本或对 main.user 的引用,只要它能保证函数不会对其进行更改即可。(我知道我们最终确实希望它被改变,但通过采用 User 类型,我们告诉编译器我们不希望它被改变)。

这种自由度允许 Zig 根据参数类型使用最优策略。像 User 这样的小类型可以通过值传递(即复制),成本较低。较大的类型通过引用传递可能更便宜。只要代码的意图得以保留,Zig 可以使用任何方法。在某种程度上,使用常量函数参数可以做到这一点。

现在你知道函数参数是常量的原因之一了吧。

也许你会想,即使与复制一个非常小的结构相比,通过引用传递怎么会更慢呢?我们接下来会更清楚地看到这一点,但要点是,当 user 是指针时,执行 user.power 会增加一点点开销。编译器必须权衡复制的代价和通过指针间接访问字段的代价。

指向指针的指针

我们之前查看了main函数中 user 的内存结构。现在我们改变了 levelUp,那么它的内存会是什么样的呢?

main:
+user -> ------------  (id: 1043368d0)  <---
+        |    1     |                      |
+        ------------  (power: 1043368d8)  |
+        |   100    |                      |
+        ------------                      |
+                                          |
+        .............  empty space        |
+        .............  or other data      |
+                                          |
+levelUp:                                  |
+user -> -------------  (*User)            |
+        | 1043368d0 |----------------------
+        -------------
+

levelUp 中,user 是指向 User 的指针。它的值是一个地址。当然不是任何地址,而是 main.user 的地址。值得明确的是,levelUp 中的 user 变量代表一个具体的值。这个值恰好是一个地址。而且,它不仅仅是一个地址,还是一个类型,即 *User。这一切都非常一致,不管我们讨论的是不是指针:变量将类型信息与地址联系在一起。指针的唯一特殊之处在于,当我们使用点语法时,例如 user.power,Zig 知道 user 是一个指针,就会自动跟随地址。

通过指针访问字段时,有些语言可能会使用不同的运算符。

重要的是要理解,levelUp函数中的user变量本身存在于内存中的某个地址。就像之前所做的一样,我们可以亲自验证这一点:

fn levelUp(user: *User) void {
+	std.debug.print("{*}\n{*}\n", .{&user, user});
+	user.power += 1;
+}
+
+

上面打印了user变量引用的地址及其值,这个值就是main函数中的user的地址。

如果user的类型是*User,那么&user呢?它的类型是**User, 或者说是一个指向User指针的指针。我可以一直这样做,直到内存溢出!

我们可以使用多级间接指针,但这并不是我们现在所需要的。本节的目的是说明指针并不特殊,它只是一个值,即一个地址和一种类型。

嵌套指针

到目前为止,User 一直很简单,只包含两个整数。很容易就能想象出它的内存,而且当我们谈论『复制』 时,也不会有任何歧义。但是,如果 User 变得更加复杂并包含一个指针,会发生什么情况呢?

pub const User = struct {
+	id: u64,
+	power: i32,
+	name: []const u8,
+};
+
+

我们已经添加了name,它是一个切片。回想一下,切片由长度和指针组成。如果我们使用名字Goku初始化user,它在内存中会是什么样子?

user -> -------------  (id: 1043368d0)
+        |     1     |
+        -------------  (power: 1043368d8)
+        |    100    |
+        -------------  (name.len: 1043368dc)
+        |     4     |
+        -------------  (name.ptr: 1043368e4)
+  ------| 1182145c0 |
+  |     -------------
+  |
+  |     .............  empty space
+  |     .............  or other data
+  |
+  --->  -------------  (1182145c0)
+        |    'G'    |
+        -------------
+        |    'o'    |
+        -------------
+        |    'k'    |
+        -------------
+        |    'u'    |
+        -------------
+

新的name字段是一个切片,由lenptr字段组成。它们与所有其他字段一起按顺序排放。在 64 位平台上,lenptr都将是 64 位,即 8 字节。有趣的是name.ptr的值:它是指向内存中其他位置的地址。

由于我们使用了字符串字面形式,user.name.ptr 将指向二进制文件中存储所有常量的区域内的一个特定位置。

通过多层嵌套,类型可以变得比这复杂得多。但无论简单还是复杂,它们的行为都是一样的。具体来说,如果我们回到原来的代码,levelUp 接收一个普通的 User,Zig 提供一个副本,那么现在有了嵌套指针后,情况会怎样呢?

答案是只会进行浅拷贝。或者像有些人说的那样,只拷贝了变量可立即寻址的内存。这样看来,levelUp 可能只会得到一个 user 残缺副本,name 字段可能是无效的。但请记住,像 user.name.ptr 这样的指针是一个值,而这个值是一个地址。它的副本仍然是相同的地址:

main: user ->    -------------  (id: 1043368d0)
+                 |     1     |
+                 -------------  (power: 1043368d8)
+                 |    100    |
+                 -------------  (name.len: 1043368dc)
+                 |     4     |
+                 -------------  (name.ptr: 1043368e4)
+                 | 1182145c0 |-------------------------
+levelUp: user -> -------------  (id: 1043368ec)       |
+                 |     1     |                        |
+                 -------------  (power: 1043368f4)    |
+                 |    100    |                        |
+                 -------------  (name.len: 1043368f8) |
+                 |     4     |                        |
+                 -------------  (name.ptr: 104336900) |
+                 | 1182145c0 |-------------------------
+                 -------------                        |
+                                                      |
+                 .............  empty space           |
+                 .............  or other data         |
+                                                      |
+                 -------------  (1182145c0)        <---
+                 |    'G'    |
+                 -------------
+                 |    'o'    |
+                 -------------
+                 |    'k'    |
+                 -------------
+                 |    'u'    |
+                 -------------
+

从上面可以看出,浅拷贝是可行的。由于指针的值是一个地址,复制该值意味着我们得到的是相同的地址。这对可变性有重要影响。我们的函数不能更改 main.user 中的字段,因为它得到了一个副本,但它可以访问同一个name,那么它能更改 name 吗?在这种特殊情况下,不行,因为 name 是常量。另外,Goku是一个字符串字面量,它总是不可变的。不过,只要花点功夫,我们就能明白浅拷贝的含义:

const std = @import("std");
+
+pub fn main() void {
+	var name = [4]u8{'G', 'o', 'k', 'u'};
+	const user = User{
+		.id = 1,
+		.power = 100,
+		// slice it, [4]u8 -> []u8
+		.name = name[0..],
+	};
+	levelUp(user);
+	std.debug.print("{s}\n", .{user.name});
+}
+
+fn levelUp(user: User) void {
+	user.name[2] = '!';
+}
+
+pub const User = struct {
+	id: u64,
+	power: i32,
+	// []const u8 -> []u8
+	name: []u8
+};
+
+

上面的代码会打印出Go!u。我们不得不将name的类型从[]const u8更改为[]u8,并且不再使用字符串字面量(它们总是不可变的),而是创建一个数组并对其进行切片。有些人可能会认为这前后不一致。通过值传递可以防止函数改变直接字段,但不能改变指针后面有值的字段。如果我们确实希望 name 不可变,就应该将其声明为 []const u8 而不是 []u8

不同编程语言有不同的实现方式,但许多语言的工作方式与此完全相同(或非常接近)。虽然所有这些看似深奥,但却是日常编程的基础。好消息是,你可以通过简单的示例和片段来掌握这一点;它不会随着系统其他部分复杂性的增加而变得更加复杂。

递归结构

有时你需要一个递归结构。在保留现有代码的基础上,我们为 User 添加一个可选的 manager 字段,类型为 ?User。同时,我们将创建两个User,并将其中一个指定为另一个的管理者:

const std = @import("std");
+
+pub fn main() void {
+	const leto = User{
+		.id = 1,
+		.power = 9001,
+		.manager = null,
+	};
+
+	const duncan = User{
+		.id = 1,
+		.power = 9001,
+		.manager = leto,
+	};
+
+	std.debug.print("{any}\n{any}", .{leto, duncan});
+}
+
+pub const User = struct {
+	id: u64,
+	power: i32,
+	manager: ?User,
+};
+
+

这段代码无法编译:struct 'learning.User' depends on itself。这个问题的根本原因是每种类型都必须在编译时确定大小,而这里的递归结构体大小是无法确定的。

我们在添加 name 时没有遇到这个问题,尽管 name可以有不同的长度。问题不在于值的大小,而在于类型本身的大小。name 是一个切片,即 []const u8,它有一个已知的大小:16 字节,其中 len 8 字节,ptr 8 字节。

你可能会认为这对任何 Optionalunion 来说都是个问题。但对于它们来说,最大字段的大小是已知的,这样 Zig 就可以使用它。递归结构没有这样的上限,该结构可以递归一次、两次或数百万次。这个次数会因User而异,在编译时是不知道的。

我们通过 name 看到了答案:使用指针。指针总是占用 usize 字节。在 64 位平台上,指针占用 8 个字节。就像Goku并没有与 user一起存储一样,使用指针意味着我们的manager不再与user的内存布局绑定。

const std = @import("std");
+
+pub fn main() void {
+	const leto = User{
+		.id = 1,
+		.power = 9001,
+		.manager = null,
+	};
+
+	const duncan = User{
+		.id = 1,
+		.power = 9001,
+		// changed from leto -> &leto
+		.manager = &leto,
+	};
+
+	std.debug.print("{any}\n{any}", .{leto, duncan});
+}
+
+pub const User = struct {
+	id: u64,
+	power: i32,
+	// changed from ?const User -> ?*const User
+	manager: ?*const User,
+};
+
+

你可能永远不需要递归结构,但这里并不是介绍数据建模的教程,因此不过多进行介绍。这里主要是想讨论指针和内存模型,以及更好地理解编译器的意图。


很多开发人员都在为指针而苦恼,因为指针总是难以捉摸。它们给人的感觉不像整数、字符串或User那样具体。虽然你现在不必完全理解这些概念,但掌握它们是值得的,而且不仅仅是为了 Zig。这些细节可能隐藏在 Ruby、Python 和 JavaScript 等语言中,其次是 C#、Java 和 Go。它影响着你如何编写代码以及代码如何运行。因此,请慢慢来,多看示例,添加调试打印语句来查看变量及其地址。你探索得越多,就会越清楚。

+ + + + \ No newline at end of file diff --git a/public/learn/preface/index.html b/public/learn/preface/index.html new file mode 100644 index 0000000..a20606a --- /dev/null +++ b/public/learn/preface/index.html @@ -0,0 +1,63 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ + +
+

欢迎阅读 Zig 编程语言入门指南《学习 Zig》。本指南旨在让你轻松掌握 Zig。本指南假定你已有编程经验,语言不限。

Zig 目前正在紧锣密鼓地开发中,Zig 语言及其标准库都在不断发展。本指南以最新的 Zig 开发版本为目标。不过,部分代码有可能编译不通过。如果你下载了最新版本的 Zig,但在运行某些代码时遇到问题,请提 issue

+ + + + \ No newline at end of file diff --git a/public/learn/stack-memory/index.html b/public/learn/stack-memory/index.html new file mode 100644 index 0000000..06fc12f --- /dev/null +++ b/public/learn/stack-memory/index.html @@ -0,0 +1,154 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ + +
+ +

Table of Contents

+
    +
  • 栈帧
  • 悬空指针
+
+

原文地址:https://www.openmymind.net/learning_zig/stack_memory

通过深入研究指针,我们了解了变量、数据和内存之间的关系。因此,我们对内存的分布有了一定的了解,但我们还没有讨论如何管理数据以及内存。对于运行时间短和简单的脚本来说,这可能并不重要。在 32GB 笔记本电脑时代,你可以启动程序,使用几百兆内存读取文件和解析 HTTP 响应,做一些了不起的事情,然后退出。程序退出时,操作系统会知道,它给程序分配的内存可以被回收,并用于其他用途了。

但对于运行数天、数月甚至数年的程序来说,内存就成了有限而宝贵的资源,很可能会被同一台机器上运行的其他进程抢占。根本不可能等到程序退出后再释放内存。这就是垃圾回收器的主要工作:了解哪些数据不再使用,并释放其内存。在 Zig 中,你就是垃圾回收器。

我们编写的大多数程序都会使用内存的三个区域。第一个是全局空间,也就是存储程序常量(包括字符串字面量)的地方。所有全局数据都被嵌入到二进制文件中,在编译时(也就是运行时)完全已知,并且不可更改。这些数据在程序的整个生命周期中都存在,从不需要增加或减少内存。除了会影响二进制文件的大小外,我们完全不必担心这个问题。

内存的第二个区域是调用栈,也是本小节的主题。第三个区域是堆,将在下一小节讨论。

三块内存区域实际上没有真正的物理差别。操作系统和可执行文件创造了“内存区域”这个概念。

栈帧

迄今为止,我们所见的所有数据都是常量,存储在二进制的全局数据部分或作为局部变量。局部表示该变量只在其声明的范围内有效。在 Zig 中,范围从花括号开始到结束。大多数变量的范围限定在一个函数内,包括函数参数,或一个控制流块,比如 if。但是,正如所见,你可以创建任意块,从而创建任意范围。

在上一部分中,我们可视化了 mainlevelUp 函数的内存,每个函数都有一个 User:

main: user ->    -------------  (id: 1043368d0)
+                 |     1     |
+                 -------------  (power: 1043368d8)
+                 |    100    |
+                 -------------  (name.len: 1043368dc)
+                 |     4     |
+                 -------------  (name.ptr: 1043368e4)
+                 | 1182145c0 |-------------------------
+levelUp: user -> -------------  (id: 1043368ec)       |
+                 |     1     |                        |
+                 -------------  (power: 1043368f4)    |
+                 |    100    |                        |
+                 -------------  (name.len: 1043368f8) |
+                 |     4     |                        |
+                 -------------  (name.ptr: 104336900) |
+                 | 1182145c0 |-------------------------
+                 -------------                        |
+                                                      |
+                 .............  empty space           |
+                 .............  or other data         |
+                                                      |
+                 -------------  (1182145c0)        <---
+                 |    'G'    |
+                 -------------
+                 |    'o'    |
+                 -------------
+                 |    'k'    |
+                 -------------
+                 |    'u'    |
+                 -------------
+

levelUp 紧接在 main 之后是有原因的:这是我们的简化版调用栈。当我们的程序启动时,main 及其局部变量被推入调用栈。当 levelUp 被调用时,它的参数和任何局部变量都会被添加到调用栈上。重要的是,当 levelUp 返回时,它会从栈中弹出。 在 levelUp 返回并且控制权回到 main 后,我们的调用栈如下所示:

main: user ->    -------------  (id: 1043368d0)
+                 |     1     |
+                 -------------  (power: 1043368d8)
+                 |    100    |
+                 -------------  (name.len: 1043368dc)
+                 |     4     |
+                 -------------  (name.ptr: 1043368e4)
+                 | 1182145c0 |-------------------------
+                 -------------                        |
+                                                      |
+                 .............  empty space           |
+                 .............  or other data         |
+                                                      |
+                 -------------  (1182145c0)        <---
+                 |    'G'    |
+                 -------------
+                 |    'o'    |
+                 -------------
+                 |    'k'    |
+                 -------------
+                 |    'u'    |
+                 -------------
+

当一个函数被调用时,其整个栈帧被推入调用栈——这就是我们需要知道每种类型大小的原因之一。尽管我们可能直到特定的代码行执行时,才能知道我们 user 的名字的长度(假设它不是一个常量字符串字面量),但我们知道我们的函数有一个 User 类型的变量,除了其他字段,只需要 8 字节来存储name.len和 8 字节来存储name.ptr

当函数返回时,它的栈帧(最后推入调用栈的帧)会被弹出。令人惊讶的事情刚刚发生:levelUp 使用的内存已被自动释放!虽然从技术上讲,这些内存可以返回给操作系统,但据我所知,没有任何实现会真正缩小调用栈(不过,在必要时,实现会动态增加调用栈)。不过,用于存储 levelUp 堆栈帧的内存现在可以在我们的进程中用于另一个堆栈帧了。

在普通程序中,调用堆栈可能会变得很大。在一个典型程序所使用的所有框架代码和库之间,你最终会发现深层嵌套的函数。通常情况下,这并不是问题,但有时你可能会遇到堆栈溢出错误。当我们的调用栈空间耗尽时,就会发生这种情况。这种情况通常发生在递归函数中,即函数会调用自身。

与全局数据一样,调用栈也由操作系统和可执行文件管理。程序启动时,以及此后启动的每个线程,都会创建一个调用栈(其大小通常可在操作系统中配置)。调用栈在程序的整个生命周期中都存在,如果是线程,则在线程的整个生命周期中都存在。程序或线程退出时,调用栈将被释放。我们的全局数据包含所有程序的全局数据,而调用栈只包含当前执行的函数层次的栈帧。这样做既能有效利用内存,又能简化堆栈帧的管理。

悬空指针

栈帧的简洁和高效令人惊叹。但它也很危险:当函数返回时,它的任何本地数据都将无法访问。这听起来似乎很合理,毕竟这是本地数据,但却会带来严重的问题。请看这段代码:

const std = @import("std");
+
+pub fn main() void {
+	const user1 = User.init(1, 10);
+	const user2 = User.init(2, 20);
+
+	std.debug.print("User {d} has power of {d}\n", .{user1.id, user1.power});
+	std.debug.print("User {d} has power of {d}\n", .{user2.id, user2.power});
+}
+
+pub const User = struct {
+	id: u64,
+	power: i32,
+
+	fn init(id: u64, power: i32) *User{
+		var user = User{
+			.id = id,
+			.power = power,
+		};
+		return &user;
+	}
+};
+
+

粗瞥一眼,预期会有下面的输出:

User 1 has power of 10
+User 2 has power of 20
+
+

但实际上:

User 2 has power of 20
+User 9114745905793990681 has power of 0
+
+

你可能会得到不同的结果,但根据我的输出,user1继承了user2的值,而user2的值是无意义的。这段代码的关键问题是User.init返回局部user的地址&user。这被称为悬空指针,是指引用无效内存的指针。它是许多段错误(segfaults)的源头。

当一个栈帧从调用栈中弹出时,我们对该内存的任何引用都是无效的。尝试访问该内存的结果是未定义的。你可能会得到无意义的数据或段错误。我们可以试图理解我的输出,但这不是我们想要或甚至可以依赖的行为。

这类错误的一个挑战是,在有垃圾回收器的语言中,上述代码完全没有问题。例如,Go 会检测局部变量 user 超出了 init 函数的作用域,并在需要时确保其有效性(Go 如何做到这一点是一个实现细节,但它有几个选项,包括将数据移动到堆中,这就是下一部分要讨论的内容)。

而另一个问题,很遗憾地说,它是一个难以发现的错误。在我们上面的例子中,我们显然返回了一个局部地址。但这种行为可以隐藏在嵌套函数和复杂数据类型中。你是否看到了以下不完整代码的任何可能问题:

fn read() !void {
+	const input = try readUserInput();
+	return Parser.parse(input);
+}
+
+

无论Parser.parse返回什么,它都将比变量input存在更久。如果Parser持有对 input 的引用,那将是一个悬空指针,等待着让我们的应用程序崩溃。理想情况下,如果 Parser 需要 input 生命周期尽可能长,它将复制 input,并且该复制将与它自己的生命周期绑定(更多内容在下一部分)。但此处没有执行这一步骤。Parser 的文档可能会对它对 input 的期望或它如何使用 input 提供一些说明。缺少这些信息,我们可能需要深入代码来弄清楚。

为了解决我们上面例子里的错误,有个简单的方法是改变 init,使它返回一个 User 而不是*User(指向 User 的指针)。我们可以使用 return user 而非 return &user。但这并不总是可能的。数据经常需要超越函数作用域的严格界限。为此,我们有了第三个内存区域–堆,这也是下一部分的主题。

在深入研究堆之前,我们要知道,在本指南结束之前,我们还将看到最后一个关于悬挂指针的示例。到那时,我们已经掌握了足够多的语言知识,可以给出一个不太复杂的示例。我之所以想重提这个话题,是因为对于来自垃圾回收语言的开发人员来说,这很可能会导致错误和挫败感。这一点你会掌握的。归根结底,就是要意识到数据的生命周期。

+ + + + \ No newline at end of file diff --git a/public/learn/style-guide/index.html b/public/learn/style-guide/index.html new file mode 100644 index 0000000..260a84c --- /dev/null +++ b/public/learn/style-guide/index.html @@ -0,0 +1,111 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ + +
+ +

Table of Contents

+
    +
  • 未使用变量 Unused Variable
  • 变量覆盖 Variable Shadowing
  • 命名规范
+
+

原文地址:https://www.openmymind.net/learning_zig/style_guide

本小节的主要内容是介绍 Zig 编译器强制遵守的 2 条规则,以及 Zig 标准库的命名惯例(naming convention)。

未使用变量 Unused Variable

Zig 编译器禁止未使用变量,例如以下代码会导致两处编译错误:

const std = @import("std");
+
+pub fn main() void {
+	const sum = add(8999, 2);
+}
+
+fn add(a: i64, b: i64) i64 {
+	// notice this is a + a, not a + b
+	return a + a;
+}
+
+

第一个编译错误,源自于sum是一个未使用的本地常量。第二个编译错误,在于在函数add的所有形参中,b是一个未使用的函数参数。对于这段代码来说,它们是比较明显的漏洞。但是在实际编程中,代码中包含未使用变量和函数形参并非完全不合理。在这种情况下,我们可以通过将未使用变量赋值给_(下划线)的方法,避免编译器报错:

const std = @import("std");
+
+pub fn main() void {
+	_ = add(8999, 2);
+
+	// or
+
+	const sum = add(8999, 2);
+	_ = sum;
+}
+
+fn add(a: i64, b: i64) i64 {
+	_ = b;
+	return a + a;
+}
+
+

除了使用_ = b之外,我们还可以直接用_来命名函数add的形参。但是,在我看来,这样做会牺牲代码的可读性,读者会猜测,这个未使用的形参到底是什么:

fn add(a: i64, _: i64) i64 {
+
+

值得注意的是,在上述例子中,std也是一个未使用的符号,但是当前这种用法并不会导致任何编译错误。可能在未来,Zig 编译器也将此视为错误。

变量覆盖 Variable Shadowing

Zig 不允许使用同名的变量。下面是一个读取 socket 的例子,这个例子包含了一个变量覆盖的编译错误:

fn read(stream: std.net.Stream) ![]const u8 {
+	var buf: [512]u8 = undefined;
+	const read = try stream.read(&buf);
+	if (read == 0) {
+		return error.Closed;
+	}
+	return buf[0..read];
+}
+
+

上述例子中,read变量覆盖了read函数。我并不太认同这个规范,因为它会导致开发者为了避免覆盖而使用短且无意义的变量名。例如,为了让上述代码通过编译,需要将变量名read改成n

我认为,这个规范并不能使代码可读性提高。在这个场景下,应该是开发者,而不是编译器,更有资格选择更有可读性的命名方案。

命名规范

除了遵守以上这些规则以外,开发者可以自由地选择他们喜欢的命名规范。但是,理解 Zig 自身的命名规范是有益的,因为大部分你需要打交道的代码,如 Zig 标准库,或者其他三方库,都采用了 Zig 的命名规范。

Zig 代码采用 4 个空格进行缩进。我个人会因为客观上更方便,使用tab键。

Zig 的函数名采用了驼峰命名法(camelCase),而变量名会采用小写加下划线(snake case)的命名方式。类型则采用的是 PascalCase 风格。除了这三条规则外,一个有趣的交叉规则是,如果一个变量表示一个类型,或者一个函数返回一个类型,那么这个变量或者函数遵循 PascalCase。在之前的章节中,其实已经见到了这个例子,不过,可能你没有注意到:

std.debug.print("{any}\n", .{@TypeOf(.{.year = 2023, .month = 8})});
+
+

我们已经看到过一些内置函数:@import@rem@intCast。因为这些都是函数,他们的命名遵循驼峰命名法。@TypeOf也是一个内置函数,但是他遵循 PascalCase,为何?因为他返回的是一个类型,因此它的命名采用了类型命名方法。当我们使用一个变量,去接收@TypeOf的返回值,这个变量也需要遵循类型命名规则(即 PascalCase):

const T = @TypeOf(3);
+std.debug.print("{any}\n", .{T});
+
+

zig 命令包含一个 fmt 子命令,在给定一个文件或目录时,它会根据 Zig 的编码风格对文件进行格式化。但它并没有包含所有上述的规则,比如它能够调整缩排,以及花括号{的位置,但是它不会调整标识符的大小写。

+ + + + \ No newline at end of file diff --git a/public/monthly/index.html b/public/monthly/index.html new file mode 100644 index 0000000..e69de29 diff --git a/public/post/first-post/fanzine.jpg b/public/post/first-post/fanzine.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9d6e569f9c2dc06fa61d7b2ebb8e3c8fe4c871fe GIT binary patch literal 124852 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�D!G8oJ(CKWoIR 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%0DwhC-?>Fc_(wk|?kH8I^_L0u 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 literal 0 HcmV?d00001 diff --git a/public/post/first-post/index.html b/public/post/first-post/index.html new file mode 100644 index 0000000..29650e0 --- /dev/null +++ b/public/post/first-post/index.html @@ -0,0 +1,90 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ +

First Post: What's A Zine?

+

This is a sample first post for your blog.

This post is defined in content/blog/first-post/ and contains the following files:

  • index.smd
  • fanzine.jpg

Another interesting thing about this post is that it uses the layouts/post.shtml template which adds at the bottom page navigation within the blog section.

Enough about Zine, let’s talk about zines.

Fanzines

A zine (short for fanzine, from “fan” + “magazine”) is a non-professional publication created by people that want to express themselves in paper form, usually in relation to a cultural phenomenon of some kind.

A photo of a black and white flyer. The main title reads 'A night of pure energy' and the rest of the page contains a mix of pictures of guitarists interleaved with other text snippets, some seemingly taken from a professional publication of some kind, and some handwritten to announce concert night dates, presumably for the musicians in the picture. +
An example of zine from 1976.

Zines in the digital era

In the digital age, some of the cultural impetus behind zines got redirected to personal blogs and similar digital, non-professional publications. The 90’s and early 00’s are famous for their whacky websites full of clip art, wordart text and “under construction” animated gifs.

This initial organic exploration didn’t last for too long as the rise of social media diverted a lot of self-expression energy towards walled gardens, a change that was also fueled by the much tighter and intense feedback loop that those platform enable.

In the 90’s the stongest dopamine hit you could get was adding a visit counter to your altavista website and watch it go up, slowly, mostly because of your own accesses. In modern times views are almost meaningless and interactions such as likes, retweets and comments provide much stronger positive feedback.

An unfortunate side effect of this new cultural wave centered around social media is that not only you end up gifting your content to platform owners, but you also participate in a system where the language of the social media site shapes your thoughts and experience in specific, and often user-hostile, ways.

Art, just like liquids, takes the shape of the container you put it in. A mobile game that lives off of in app purchases will never be truly great because of the tension between making the game entertaining enough to keep players engaged, and the need to make it boring enough so that they will want to buy upgrades to make the game more fun.

Similarly, self-expression on Twitter is encouraged to take the shape of short, hyperbolic hot takes that forgo nuance in order to create catchy quips that can be used for hasty decision making.

Likewise, corpowave never goes out of style on LinkedIn. Spend enough time in there and you will become a character from Severance.

We’re not in 1990 anymore

Despite all the issues with social media, there is no point in thinking of the 90’s as a better time. It was not. And despite the winks at the past, Zine is not a tool for indulging in nostalgia.

The goal is to make art: the act of inducing a change in others through our self-expression.

You could argue that the 90’s excelled at self-expression, but in doing so you would also have to accept that social media is infinitely more effective at inducing change in others (albeit at the expense of freedom of expression).

Once you realize that, the path forward is clear:

  1. Own your content.
  2. Create new social systems that optimize for creating art over engagement.

Owning your content means that you will be unaffected by enshittification of platforms that would otherwise keep your data hostage. It also is the single most effective thing you can do as an individual to take power away from platforms, all while protecting your own immediate interests.

Creation of new social systems is a slightly more hairy problem than self hosting a static website, but it’s something that can be done. Over the years we’ve had plenty of social outlets that have allowed people to socialize through their homemade games, music, drawings, fanfics, etc; and chances are that we have yet many more of these outlets ahead of us to create.

Zine gives you a small puzzle piece to help you inch closer toward a better future, partially by providing you with a new iteration over tried and true patterns (e.g. by facilitating content creation by separating content from layouting concerns as much as possible), and also by being a bit experimental with the concept of a devlog, something that you wouldn’t normally expect to find on a static website.

Lastly, Zine makes sure your content (both blog and devlog, but also any other content format you might come up with yourself) is available via RSS syndication. RSS feeds are far from a winning technology in the fight against the ebb and enshittyflow of social media, but they are another small puzzle piece that costs nothing to maintain and that might turn out to be critical once enough other preconditions are met.

With that in mind, go make art with your words.

– Loris

+ + + + + \ No newline at end of file diff --git a/public/post/index.html b/public/post/index.html new file mode 100644 index 0000000..643737e --- /dev/null +++ b/public/post/index.html @@ -0,0 +1,100 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ +

Blog

+

This page defines the blog section and lists all posts in it.

A “site section” in Zine is a group of pages that form a logical subtree of the website. It’s related to directory structure, but it’s not an entirely 1:1 mapping.

What defines a site section in Zine is the presence of index.smd files. You can learn more in the official Zine docs.

Take also a look at layouts/blog.shtml to get an idea of how to render a page list in a SuperHTML template.

The blog section also has an RSS feed.

In Zine, RSS feeds are considered “alternative” versions of an existing page. In concrete defines the blog section and that lists all pages in it, is rendered in two versions: HTML for human readers, and XML for RSS readers.

This is the SuperMD frontmatter code that defines the RSS feed:

.alternatives = [{ 
+    .name = "rss",
+    .layout = "rss.xml", 
+    .output = "index.xml",
+}],
+
+

(btw syntax highlighting is done statically in Zine, no need for javascript libraries, unless you want to)

+ + + + + \ No newline at end of file diff --git a/public/post/index.xml b/public/post/index.xml new file mode 100644 index 0000000..11b0433 --- /dev/null +++ b/public/post/index.xml @@ -0,0 +1,32 @@ + + + Zig 语言中文社区 + https://example.com + Zig 语言中文社区 - Blog + Zine -- https://zine-ssg.io + en-US + Sat, 28 Jun 2025 03:40:44 +0000 + + + Second Post + <p>This second post is mainly here to show you that you can also create single file posts for convenience. The first post contains more interesting content.</p><p>Don’t forget to read <a href="https://zine-ssg.io/docs/supermd/" target="_blank">the official SuperMD docs</a> to know how to <em>style</em> your content.</p><p>Btw this sample website also includes the JS/CSS dependencies required to render math:</p><script type="math/tex">\begin{aligned} +f(t) &= \int_{-\infty}^\infty F(\omega) \cdot (-1)^{2 \omega t} \mathrm{d}\omega \\ +F(\omega) &= \int_{-\infty}^\infty f(t) \div (-1)^{2 \omega t} \mathrm{d}t \\ +\end{aligned} +</script><p>This: <script type="math/tex">(-1)^x = \cos(\pi x) + i\sin(\pi x)</script> is an inline equation instead!</p> + https://example.com/post/second-post/ + Tue, 02 Jan 1990 00:00:00 +0000 + https://example.com/post/second-post/ + + + + First Post: What's A Zine? + <p>This is a sample first post for your blog.</p><p>This post is defined in <code>content/blog/first-post/</code> and contains the following files:</p><ul><li><code>index.smd</code></li><li><code>fanzine.jpg</code></li></ul><p>Another interesting thing about this post is that it uses the <code>layouts/post.shtml</code> template which adds at the bottom page navigation within the blog section.</p><p>Enough about Zine, let’s talk about zines.</p><h2>Fanzines</h2><p>A zine (short for <em>fanzine</em>, from “fan” + “magazine”) is a non-professional publication created by people that want to express themselves in paper form, usually in relation to a cultural phenomenon of some kind.</p><p><figure><img src="https://example.com/post/first-post/fanzine.jpg" alt="A photo of a black and white flyer. The main title reads 'A night of pure energy' and the rest of the page contains a mix of pictures of guitarists interleaved with other text snippets, some seemingly taken from a professional publication of some kind, and some handwritten to announce concert night dates, presumably for the musicians in the picture."> +<figcaption>An example of zine from 1976.</figcaption></figure></p><h2>Zines in the digital era</h2><p>In the digital age, some of the cultural impetus behind zines got redirected to personal blogs and similar digital, non-professional publications. The 90’s and early 00’s are famous for their whacky websites full of clip art, wordart text and “under construction” animated gifs.</p><p>This initial organic exploration didn’t last for too long as the rise of social media diverted a lot of self-expression energy towards walled gardens, a change that was also fueled by the much tighter and intense feedback loop that those platform enable.</p><p>In the 90’s the stongest dopamine hit you could get was adding a visit counter to your altavista website and watch it go up, slowly, mostly because of your own accesses. In modern times views are almost meaningless and interactions such as <em>likes</em>, <em>retweets</em> and <em>comments</em> provide much stronger positive feedback.</p><p>An unfortunate side effect of this new cultural wave centered around social media is that not only you end up gifting your content to platform owners, but you also participate in a system where the <em>language</em> of the social media site shapes your thoughts and experience in specific, and often user-hostile, ways.</p><p>Art, just like liquids, takes the shape of the container you put it in. A mobile game that lives off of in app purchases will never be truly great because of the tension between making the game entertaining enough to keep players engaged, and the need to make it boring enough so that they will want to buy upgrades to make the game more fun.</p><p>Similarly, self-expression on Twitter is encouraged to take the shape of short, hyperbolic hot takes that forgo nuance in order to create catchy quips that can be used for hasty decision making.</p><p>Likewise, <span class="wave ">corpowave</span> never goes out of style on LinkedIn. Spend enough time in there and you will become a character from Severance.</p><h2>We’re not in 1990 anymore</h2><p>Despite all the issues with social media, there is no point in thinking of the 90’s as a better time. It was not. And despite the winks at the past, Zine is not a tool for indulging in nostalgia.</p><p><strong>The goal is to make art</strong>: the act of inducing a change in others through our self-expression.</p><p>You could argue that the 90’s excelled at self-expression, but in doing so you would also have to accept that social media is infinitely more effective at inducing change in others (albeit at the expense of freedom of expression).</p><p>Once you realize that, the path forward is clear:</p><ol><li>Own your content.</li><li>Create new social systems that optimize for creating art over engagement.</li></ol><p>Owning your content means that you will be unaffected by enshittification of platforms that would otherwise keep your data hostage. It also is the single most effective thing you can do as an individual to take power away from platforms, all while protecting your own immediate interests.</p><p>Creation of new social systems is a <em>slightly more hairy</em> problem than self hosting a static website, but it’s something that can be done. Over the years we’ve had plenty of social outlets that have allowed people to socialize through their homemade games, music, drawings, fanfics, etc; and chances are that we have yet many more of these outlets ahead of us to create.</p><p>Zine gives you a small puzzle piece to help you inch closer toward a better future, partially by providing you with a new iteration over tried and true patterns (e.g. by facilitating content creation by separating content from layouting concerns as much as possible), and also by being a bit experimental with the concept of a devlog, something that you wouldn’t normally expect to find on a static website.</p><p>Lastly, Zine makes sure your content (both blog and devlog, but also any other content format you might come up with yourself) is available via RSS syndication. RSS feeds are far from a winning technology in the fight against the ebb and <em>enshitty</em>flow of social media, but they are another small puzzle piece that costs nothing to maintain and that might turn out to be critical once enough other preconditions are met.</p><p>With that in mind, <strong>go make art with your words</strong>.</p><p>– Loris</p> + https://example.com/post/first-post/ + Mon, 01 Jan 1990 00:00:00 +0000 + https://example.com/post/first-post/ + + + + diff --git a/public/post/second-post/index.html b/public/post/second-post/index.html new file mode 100644 index 0000000..de54894 --- /dev/null +++ b/public/post/second-post/index.html @@ -0,0 +1,93 @@ + + + + + + + Zig 语言中文社区 + + + + + + + + + +
+

Zig 语言中文社区

+ +
+ +

Second Post

+

This second post is mainly here to show you that you can also create single file posts for convenience. The first post contains more interesting content.

Don’t forget to read the official SuperMD docs to know how to style your content.

Btw this sample website also includes the JS/CSS dependencies required to render math:

This: is an inline equation instead!

+ + + + + \ No newline at end of file diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..d4e5094 --- /dev/null +++ b/public/style.css @@ -0,0 +1,47 @@ +body { + margin: auto; + max-width: 60vw; + font-size: 1.5rem; + line-height: 1.5; +} + +a{ + color: inherit; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; +} +.site-title { + display: inline-block; + text-decoration: none; + color: inherit; +} +nav { + display: inline-block; +} +nav a { + text-decoration: none; + font-size: large; +} + +footer > div { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +footer > div div { + margin: auto 0; +} + +footer > div i { + font-size: 24px; + margin: 0 5px; +} + +footer > div a { + text-decoration: none; +} diff --git a/public/zigcc.svg b/public/zigcc.svg new file mode 100644 index 0000000..85801dc --- /dev/null +++ b/public/zigcc.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/favicons/android-chrome-192x192.png b/static/favicons/android-chrome-192x192.png deleted file mode 100644 index e1b6d95700f9de70396df3ba3a575f9b26ff184b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3627 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 NqaSbP4=#jdzz+r^=~(~( diff --git a/zine.ziggy b/zine.ziggy new file mode 100644 index 0000000..bcf07f9 --- /dev/null +++ b/zine.ziggy @@ -0,0 +1,9 @@ +Site { + .title = "Zig 语言中文社区", + .host_url = "https://example.com",// TODO + .content_dir_path = "content", + .layouts_dir_path = "layouts", + .assets_dir_path = "assets", + .static_assets = [ + ], +} From d83fb6af5cb9407721ea97310b877c47b7702b74 Mon Sep 17 00:00:00 2001 From: xihale Date: Mon, 30 Jun 2025 21:53:44 +0800 Subject: [PATCH 02/22] Refactor markdown to SMD conversion script and update configurations. Changed source directory and author details, improved frontmatter handling, and enhanced error logging. Removed outdated posts and images, and added new content for the blog section. Updated styles in CSS files for better layout and presentation. --- assets/highlight.css | 2 + assets/style.css | 14 +- content/post/0.14.smd | 194 ++++ content/post/2023-09-05-bog-gc-1-en.smd | 200 ++++ content/post/2023-09-05-bog-gc-1.smd | 194 ++++ content/post/2023-09-05-hello-world.smd | 30 + content/post/2023-09-21-zig-midi.smd | 988 ++++++++++++++++++ .../2023-12-24-zig-build-explained-part1.smd | 337 ++++++ .../2023-12-28-zig-build-explained-part2.smd | 563 ++++++++++ .../2023-12-29-zig-build-explained-part3.smd | 504 +++++++++ ...2-how-to-release-your-zig-applications.smd | 182 ++++ content/post/2024-04-06-zig-cpp.smd | 168 +++ content/post/2024-05-07-package-hash.smd | 239 +++++ content/post/2024-05-24-interface-idioms.smd | 446 ++++++++ content/post/2024-06-10-zig-hashmap-1.smd | 388 +++++++ content/post/2024-06-11-zig-hashmap-2.smd | 374 +++++++ .../2024-06-16-leveraging-zig-allocator.smd | 163 +++ content/post/2024-08-12-zoop.smd | 486 +++++++++ content/post/2024-11-26-typed-fsm.smd | 556 ++++++++++ content/post/2025-01-23-bonkers-comptime.smd | 391 +++++++ content/post/first-post/fanzine.jpg | Bin 124852 -> 0 bytes content/post/first-post/index.smd | 110 -- content/post/index.smd | 39 +- content/post/news/2023-12-11-first-meetup.smd | 53 + .../post/news/2023-12-27-second-meetup.smd | 70 ++ content/post/news/2024-01-14-third-meetup.smd | 62 ++ .../news/2024-04-27-release-party-review.smd | 58 + content/post/news/index.smd | 17 + content/post/second-post.smd | 28 - convert_md_to_smd.js | 241 ++--- layouts/monthly.shtml | 2 +- layouts/post.shtml | 29 +- 32 files changed, 6817 insertions(+), 311 deletions(-) create mode 100644 content/post/0.14.smd create mode 100644 content/post/2023-09-05-bog-gc-1-en.smd create mode 100644 content/post/2023-09-05-bog-gc-1.smd create mode 100644 content/post/2023-09-05-hello-world.smd create mode 100644 content/post/2023-09-21-zig-midi.smd create mode 100644 content/post/2023-12-24-zig-build-explained-part1.smd create mode 100644 content/post/2023-12-28-zig-build-explained-part2.smd create mode 100644 content/post/2023-12-29-zig-build-explained-part3.smd create mode 100644 content/post/2024-01-12-how-to-release-your-zig-applications.smd create mode 100644 content/post/2024-04-06-zig-cpp.smd create mode 100644 content/post/2024-05-07-package-hash.smd create mode 100644 content/post/2024-05-24-interface-idioms.smd create mode 100644 content/post/2024-06-10-zig-hashmap-1.smd create mode 100644 content/post/2024-06-11-zig-hashmap-2.smd create mode 100644 content/post/2024-06-16-leveraging-zig-allocator.smd create mode 100644 content/post/2024-08-12-zoop.smd create mode 100644 content/post/2024-11-26-typed-fsm.smd create mode 100644 content/post/2025-01-23-bonkers-comptime.smd delete mode 100644 content/post/first-post/fanzine.jpg delete mode 100644 content/post/first-post/index.smd create mode 100644 content/post/news/2023-12-11-first-meetup.smd create mode 100644 content/post/news/2023-12-27-second-meetup.smd create mode 100644 content/post/news/2024-01-14-third-meetup.smd create mode 100644 content/post/news/2024-04-27-release-party-review.smd create mode 100644 content/post/news/index.smd delete mode 100644 content/post/second-post.smd diff --git a/assets/highlight.css b/assets/highlight.css index 754422e..b44c718 100644 --- a/assets/highlight.css +++ b/assets/highlight.css @@ -261,3 +261,5 @@ code.conf .comment { color: var(--comment-gray); /* ; comment or # comment */ font-style: italic; } + +/* TODO: cpp support */ \ No newline at end of file diff --git a/assets/style.css b/assets/style.css index c58daec..5c4aa06 100644 --- a/assets/style.css +++ b/assets/style.css @@ -181,4 +181,16 @@ html { html { scrollbar-color: #444 var(--bg); } -} \ No newline at end of file +} + +#meta p{ + margin: 0; +} + +#meta #author::before{ + content: "借由:"; +} + +h1{ + margin-bottom: 0; +} diff --git a/content/post/0.14.smd b/content/post/0.14.smd new file mode 100644 index 0000000..ff19cda --- /dev/null +++ b/content/post/0.14.smd @@ -0,0 +1,194 @@ +--- +.title = "0.14 版本更新介绍", +.date = @date("2025-05-21T09:26:22+08:00"), +.author = "ZigCC", +.layout = "post.shtml", +.draft = false, +--- + +https://ziglang.org/download/0.14.0/release-notes.html + +# 发布概览 + +Zig 0.14.0 版本是经过 **9 个月的工作**,由 **251 位不同的贡献者** 完成,包含 **3467 个提交** 的成果。该版本专注于提升 **健壮性**、**最优性** 和 **可重用性**,并通过 Zig 软件基金会 (Zig Software Foundation) 资助开发。 + +# 核心主题与重要更新 + +1. **提升编译速度与开发效率:** + +- 版本说明强调了两个重要的长期投资:**增量编译 (Incremental Compilation)** 和 **快速 x86 后端 (fast x86 Backend)**。 +- 这两项投资的核心目标是 **“reducing edit/compile/debug cycle latency”**(减少编辑/编译/调试循环延迟)。 +- **增量编译** 功能在本次发布中可以通过 -fincremental 标志选择启用,但目前尚未完全成熟。它在配合文件系统监控时表现良好,尤其是在仅检查编译错误时能显著提升反馈速度。 +- 引用:$ zig build -Dno-bin -fincremental --watch (展示了在大型代码库中快速获得编译错误反馈的示例)。 +- 目前不兼容 usingnamespace,建议用户尽量避免使用。 +- **x86 后端** 在行为测试中表现出色,已经通过了 98% 的测试用例,编译速度显着快于 LLVM 后端,并且支持更好的调试器。它有望在下一个发布周期成为调试模式下的默认后端。可以通过 -fno-llvm 或 use\_llvm = false 启用。 +- 引用:The x86 backend is now passing 1884/1923 (98%) of the behavior test suite compared to the LLVM backend. + +2. **增强目标平台支持 (Target Support):** + +- 这是一个主要主题,Zig 的跨平台编译能力得到了极大扩展。 +- 版本说明详细列出了不同目标平台在语言特性、标准库、代码生成、链接器、调试信息、libc 和 CI 测试等方面的支持级别,采用分层系统 (Tier System) 进行分类(Tier 1 为最高级别)。 +- 引用:A major theme in this Zig release is improved target support; the list of targets that Zig can correctly cross-compile to and run on has been greatly expanded. +- 对 arm/thumb, mips/mips64, powerpc/powerpc64, riscv32/riscv64, 或 s390x 等目标平台的工具链问题、标准库支持缺失和崩溃问题有了显著改进。 +- 更新了目标三元组 (Target Triple Changes) 的命名和支持,以更准确地反映不同平台(如 Windows、Linux 使用 musl libc)的 ABI 和特性。 + +3. **重要的语言特性变化 (Language Changes):** + +- **标记 switch (Labeled Switch):** 允许 switch 语句拥有标签并被 continue 语句指向,从而实现更清晰的状态机实现。关键动机在于生成优化后的代码,特别是通过不同的分支指令帮助 CPU 进行更准确的分支预测,提升性能。 +- 引用:Zig 0.14.0 implements an accepted proposal which allows switch statements to be labeled, and to be targeted by continue statements. +- 引用:This language construct is designed to generate code which aids the CPU in predicting branches between cases of the switch, allowing for increased performance in hot loops... +- 更新 Zig 的 tokenizer 利用此特性带来了 13% 的性能提升。 +- **声明字面量 (Decl Literals):** 扩展了 .foo 语法,不仅可以引用枚举成员,还可以引用目标类型上的任何声明 (const/var/fn)。这使得初始化结构体字段和调用初始化函数更加简洁和安全,特别是避免了无效的默认字段值问题。 +- 引用:Zig 0.14.0 extends the "enum literal" syntax (.foo) to provide a new feature, known as "decl literals". +- 许多现有使用字段默认值的地方可能更适合使用 default 或 empty 等声明来处理,以确保数据不变性。 +- **字段和声明不能共享名称 (Fields and Declarations Cannot Share Names):** 引入了容器类型(struct, union, enum, opaque)的字段和声明不能同名的限制,解决了歧义问题并便于文档生成。 +- 引用:Zig 0.14.0 introduces a restriction that container types (struct , union , enum and opaque ) cannot have fields and declarations ( const / var / fn ) with the same names. +- **@splat 支持数组 (@splat Supports Arrays):** 扩展了 @splat 内置函数,使其可以应用于数组和哨兵终止数组,方便用常量值初始化数组。 +- 引用:Zig 0.14.0 expands the @splat builtin to apply not only to vectors, but to arrays. +- **全局变量可以相互引用地址 (Global Variables can be Initialized with Address of Each Other):** 允许全局变量在初始化时相互引用地址。 +- **@export 操作数现在是指针 (@export Operand is Now a Pointer):** @export 内置函数现在接受一个指针作为操作数,使其用法更清晰和一致,通常只需在旧用法前添加 &。 +- **新的 @branchHint 内置函数,取代 @setCold (New @branchHint Builtin, Replacing @setCold):** 引入 @branchHint 内置函数,允许开发者向优化器提示分支的可能性,有 .none, .likely, .unlikely, .cold, .unpredictable 等选项。这取代了旧的 @setCold。 +- 引用:Zig 0.14.0 introduces a mechanism to communicate this information: the new @branchHint(comptime hint: std.builtin.BranchHint) builtin. +- @branchHint 必须是其所在块或函数的第一个语句。 +- **移除 @fenceStoreLoad Barriers:** 移除 @fence(.StoreLoad),其功能现在可以通过使用 SeqCst 或 Acquire/Release 原子操作来实现。 +- **Packed Struct Equality 和 Packed Struct Atomics:** 允许直接对 Packed Struct 进行相等性比较和原子操作,不再需要 @bitCast 到底层整数类型。 +- **@ptrCast 允许改变切片长度 (@ptrCast Allows Changing Slice Length):** #22706 +- **移除匿名结构体类型,统一元组 (Remove Anonymous Struct Types, Unify Tuples):** 重构匿名结构体字面量和元组的工作方式,使其使用“普通”结构体类型和基于 AST 节点及结构体的等价性。 +- **Calling Convention 增强和 @setAlignStack 被取代 (Calling Convention Enhancements and @setAlignStack Replaced):** std.builtin.CallingConvention 现在是一个标记联合,包含更多目标平台特定的调用约定,并允许通过 CommonOptions 设置栈对齐等选项。.c 调用约定现在是一个声明,可以通过 callconv(.c) 使用 Decl Literals 访问。@setAlignStack 被移除,其功能现在通过调用约定的选项实现。 +- \*_std.builtin.Type 字段重命名和简化 (std.builtin.Type Fields Renamed and Simplify Usage Of ?const anyopaque):_ std.builtin.Type 联合体的字段名称改为小写,并增加了对 default\_value\_ptr 和 sentinel\_ptr 字段的 helper 方法,以简化使用。 +- **不允许非标量哨兵类型 (Non-Scalar Sentinel Types Disallowed):** 哨兵值现在只能是支持 == 操作符的标量类型。 +- **@FieldType 内置函数 (@FieldType builtin):** 新增 @FieldType 内置函数,用于获取给定类型和字段名称的字段类型,取代了 std.meta.FieldType 函数。 +- **@src 获得 Module 字段 (@src Gains Module Field):** std.builtin.SourceLocation 结构体新增 module 字段。 +- **@memcpy 规则调整 ( @memcpy Rules Adjusted):** langspec 定义调整,源和目标元素类型必须内存可强制转换,从而确保是原始复制操作。对 comptime @memcpy 增加了别名检查和更高效的实现。 +- **禁止不安全的内存强制转换 (Unsafe In-Memory Coercions Disallowed):** #22243 +- **callconv, align, addrspace, linksection 不能引用函数参数 (#22264callconv, align, addrspace, linksection Cannot Reference Function Arguments):** #22264 +- **函数调用的分支配额规则调整 (Branch Quota Rules Adjusted for Function Calls):** #22414 + +4. **标准库改进 (Standard Library):** + +- **DebugAllocator 和 SmpAllocator:** 重写了 GeneralPurposeAllocator 并更名为 DebugAllocator,提高了调试模式下的性能。新增了 SmpAllocator,一个针对 ReleaseFast 模式和多线程优化的单例分配器,性能可与 glibc 媲美。 +- **Allocator API 变化 (remap):** std.mem.Allocator.VTable 新增了 remap 函数,允许在可能的情况下进行无需 memcpy 的内存重映射。resize 语义不变。Allocator.VTable 函数现在使用 std.mem.Alignment 类型。 +- **ZON 解析和序列化 (ZON Parsing and Serialization):** std.zon.parse 提供运行时解析 ZON 到 Zig 结构体的功能,std.zon.stringify 提供运行时序列化功能。 +- **运行时页面大小 (Runtime Page Size):** 移除了编译时已知的 std.mem.page\_size,代之以编译时已知的 std.heap.page\_size\_min 和 std.heap.page\_size\_max。std.heap.pageSize() 提供运行时实际页面大小。解决了在 Apple 新硬件上运行 Linux 的支持问题。 +- **Panic 接口 (#22594Panic Interface):** #22594 +- **Transport Layer Security (std.crypto.tls):** #21872 +- **process.Child.collectOutput API 变化 (#21872process.Child.collectOutput API Changed):** API 签名改变,现在将 allocator 作为第一个参数传入。 +- **LLVM Builder API:** LLVM bitcode builder API 移至 std.zig.llvm,方便第三方项目复用。 +- **拥抱“非管理”风格容器 (Embracing "Unmanaged"-Style Containers):** 大部分带有内置 allocator 的标准库容器(如 std.ArrayList, std.ArrayHashMap)已被弃用,推荐使用“非管理”风格容器(如 std.ArrayListUnmanaged, std.ArrayHashMapUnmanaged),并在需要时显式传递 allocator。 +- **std.c 重组 (std.c Reorganization):** 重组了 std.c,使其结构更清晰,并改变了对不存在符号的处理方式(从 @compileError 改为 void 或 {}),移除了标准库中最后一个 usingnamespace 的使用点。 +- **弃用列表 (List of Deprecations):** 列出了大量被弃用或重命名的标准库函数和类型。 +- **Binary Search:** #20927 +- **std.hash\_map 获得 rehash 方法 (#20927std.hash\_map gains a rehash method):** 为解决 HashMap 移除元素后的性能下降问题新增了 rehash 方法(Array Hash Map 没有此问题)。此方法预计在未来不再需要时会被删除。 + +5. **构建系统升级 (Build System):** + +- **基于现有模块创建 Artifacts (Creating Artifacts from Existing Modules):** 修改了构建系统 API,允许从现有的 std.Build.Module 对象创建 Compile 步骤,使模块图的定义更清晰,组件复用更容易。旧的 API 用法已被弃用。 +- 引用:Zig 0.14.0 modifies the build system APIs for creating Compile steps, allowing them to be created from existing std.Build.Module objects. +- **允许包按名称暴露任意 LazyPath (Allow Packages to Expose Arbitrary LazyPaths by Name):** 新增 std.Build.addNamedLazyPath 和 std.Build.Module.namedLazyPath 方法,允许依赖包按名称暴露生成的 LazyPath 给其依赖者使用。 +- **addLibrary 函数:** 新增 addLibrary 函数,取代 addSharedLibrary 和 addStaticLibrary,使得在 build.zig 中更容易切换链接模式,并与 linkLibrary 名称更匹配。 +- **文件系统监控 (File System Watching):** #22105 +- **新包哈希格式 (New Package Hash Format):** 引入新的包哈希格式,包含 32 位 id 和 32 位校验和,用于在去中心化生态系统中唯一标识包。当 fork 项目时,如果上游仍维护,应该重新生成 fingerprint。 +- **WriteFile Step, RemoveDir Step, Fmt Step:** 新增或改进了这些构建步骤。 +- **Breakings:** 多个 installHeader 和 installHeadersDirectory 相关函数签名改变,现在接受 LazyPath。生成的 -femit-h 头文件不再默认发出。 + +6. **编译器和链接器改进 (Compiler and Linker):** + +- **多线程后端支持 (Multithreaded Backend Support):** 部分编译器后端(如 x86 后端)支持在单独线程中运行代码生成,显著提升了编译速度。 +- **LLVM 19:** 升级到 LLVM 19.1.7。 +- **链接器输入文件解析移至前端 (Move Input File Parsing to the Frontend):** 将 GNU ld 脚本处理移至前端,以便在编译开始时了解所有链接器输入,实现编译和链接同时进行。为了避免对所有 .so 文件进行文件系统访问,新增了 -fallow-so-scripts 命令行标志,允许用户选择启用对 .so 脚本的支持。 +- 引用:Moves GNU ld script processing to the frontend to join the relevant library lookup logic, making the libraries within subject to the same search criteria as all the other libraries. + +7. **集成模糊测试器 (Fuzzer):** + +- Zig 0.14.0 集成了一个模糊测试器,目前处于 alpha 状态。通过 --fuzz 命令行选项启用。 +- 可以针对包含 std.testing.fuzz 的单元测试二进制文件进行进程内模糊测试。 +- 提供了 Web UI (http://127.0.0.1:38239/) 显示实时代码覆盖率。 + +8. **Bug 修复与 Toolchain 更新 (Bug Fixes and Toolchain):** + +- 关闭了 416 个 bug 报告。但版本说明坦承 **“This Release Contains Bugs”**,并指出在 1.0.0 版本达到 Tier 1 支持时会增加 bug 策略。 +- **UBSan Runtime:** Debug 模式下默认启用 UBSan 运行时库,为 C 代码的未定义行为提供更详细的恐慌信息和堆栈跟踪。可以通过 -fno-ubsan-rt 和 -fubsan-rt 控制。 +- **compiler\_rt:** 包含了优化的 memcpy 实现。 +- **musl 1.2.5:** 捆绑的 musl 更新并应用了 CVE 修复和目标平台特定补丁。不再捆绑 musl 的 memcpy 文件,而是使用 Zig 的优化实现。 +- **glibc 2.41:** 支持 glibc 2.40 和 2.41 交叉编译,修复了多个与 glibc 相关的问题。 +- **Linux 6.13.4 Headers, Darwin libSystem 15.1, MinGW-w64, wasi-libc:** 更新了捆绑的操作系统头文件和 libc 版本。捆绑了 winpthreads 库。 + +**社区贡献与资助:** + +- 版本说明详细感谢了 **251 位** 为本次发布做出贡献的开发者。 +- 特别感谢了通过经常性捐赠支持 Zig 的个人和组织赞助商,强调了开源社区驱动的重要性。 + +**路线图展望:** + +- 版本说明中穿插提到了未来的一些计划,例如提升增量编译的成熟度、使 x86 后端成为调试模式下的默认选项、改进 ZON 导入,以及未来容器和哈希地图的改进。 +- 最终目标是达到 1.0.0 版本,届时 Tier 1 支持将包含 bug 政策。 + + +# 关于 Zig 0.14.0 版本的常见问题解答 + +## Zig 0.14.0 版本的主要更新和亮点是什么? + +Zig 0.14.0 版本是长达 9 个月开发工作和 3467 次提交的成果,主要亮点包括:显著增强了对多种目标平台的支持,包括 arm/thumb、mips/mips64、powerpc/powerpc64、riscv32/riscv64 和 s390x 等,许多之前存在工具链问题、标准库支持缺失或崩溃的情况现在应该可以正常工作了。此外,该版本在构建系统方面进行了大量升级,并对语言进行了多项重要改进,例如引入了 Labeled Switch 和 Decl Literals 等新特性。为了缩短编辑/编译/调试周期,版本还迈向了两个长期投资目标:增量编译和快速 x86 后端。 + +## Zig 如何对不同目标平台的开发支持进行分级? + +Zig 使用四层系统来对不同目标平台的支持级别进行分类,其中 Tier 1 是最高级别: + +- **Tier 1:** 所有非实验性语言特性都能正常工作。编译器能够独立生成目标平台的机器代码,功能与 LLVM 相当。即使在交叉编译时,该目标平台也有可用的 libc。 +- **Tier 2:** 标准库的跨平台抽象在该目标平台上有实现。该目标平台具备调试信息能力,可以在断言失败和崩溃时生成堆栈跟踪。CI 机器在每次 master 分支提交时都会自动构建和测试该目标平台。 +- **Tier 3:** 编译器可以依赖外部后端(如 LLVM)为该目标平台生成机器代码。链接器可以为该目标平台生成目标文件、库和可执行文件。 +- **Tier 4:** 编译器可以依赖外部后端(如 LLVM)为该目标平台生成汇编源代码。如果 LLVM 将此目标平台视为实验性,则需要从源代码构建 LLVM 和 Zig 才能使用它。 + +## 什么是 Labeled Switch,它有什么优势? + +Labeled Switch 是 Zig 0.14.0 中引入的一项语言特性,允许 switch 语句被标记,并作为 continue 语句的目标。continue :label value 语句会用 value 替换原始的 switch 表达式操作数,并重新评估 switch。尽管在语义上类似于循环中的 switch,但 Labeled Switch 的关键优势在于其代码生成特性。它可以生成帮助 CPU 更准确预测分支的代码,从而提高热循环中的性能,特别是在处理指令分派、评估有限状态自动机 (FSA) 或执行类似基于 case 的评估时。这有助于 branch predictor 更准确地预测控制流。 + +## Decl Literals 是什么,它解决了哪些问题? + +Decl Literals 是 Zig 0.14.0 扩展 "enum literal" 语法 (.foo) 而引入的新特性。现在,一个枚举字面量 .foo 不仅可以引用枚举变体,还可以使用 Result Location Semantics 引用目标类型上的任何声明(const/var/fn)。这在初始化结构体字段时特别有用,可以避免重复指定类型,并有助于避免 Faulty Default Field Values 的问题,确保数据不变量不会因覆盖单个字段而受到破坏。它也支持直接调用函数来初始化值。 + +## Zig 0.14.0 版本在内存分配器方面有哪些值得关注的变化? + +该版本对内存分配器进行了多项改进: + +- **DebugAllocator:** GeneralPurposeAllocator 已被重写并更名为 DebugAllocator,以解决其依赖于编译时已知的页面大小的问题,并提升性能。 +- **SmpAllocator:** 引入了一个新的分配器,专为 ReleaseFast 优化模式和多线程环境设计。它是一个单例,使用全局状态,每个线程拥有独立的空闲列表,并通过原子操作处理线程资源回收,即使线程退出也能恢复数据。其性能与 glibc 相当。 +- **Allocator API Changes (remap):** std.mem.Allocator.VTable 引入了一个新的 remap 函数,允许尝试扩展或收缩内存并可能重新定位,如果无法在不执行内部 memcpy 的情况下完成,则返回 null,提示调用者自行处理复制。同时,resize 函数保持不变。Allocator.VTable 中的所有函数现在使用 std.mem.Alignment 类型代替 u8,增加了类型安全。 +- **Runtime Page Size:** 移除了编译时已知的 std.mem.page\_size,代之以编译时已知的页面大小上下界 std.heap.page\_size\_min 和 std.heap.page\_size\_max。运行时获取页面大小可以使用 std.heap.pageSize(),它会优先使用编译时已知的值,否则在运行时查询操作系统并缓存结果。这修复了对 Asahi Linux 等新硬件上运行 Linux 的支持。 + +## Zig 0.14.0 版本如何改进构建系统,特别是处理模块和依赖关系? + +Zig 0.14.0 版本在构建系统方面有多项重要改进: + +- **Creating Artifacts from Existing Modules:** 修改了构建系统 API,允许从现有的 std.Build.Module 对象创建 Compile 步骤。这使得模块图的定义更加清晰,并且可以更容易地重用图中的组件。 +- **Allow Packages to Expose Arbitrary LazyPaths by Name:** 引入了 std.Build.Step.addNamedLazyPath 方法,允许包暴露命名的 LazyPath,例如生成的源代码文件,供依赖包使用。 +- **New Package Hash Format:** 引入了新的包哈希格式,包括 name、version 和 fingerprint 字段。fingerprint 是一个重要的概念,用于全局唯一标识包,即使在去中心化的生态系统中也能准确识别更新版本。 +- **addLibrary Function:** 引入 addLibrary 函数作为 addSharedLibrary 和 addStaticLibrary 的替代,允许在 build.zig 中更容易地切换链接模式,并与 linkLibrary 函数名称保持一致。 +- **Import ZON:** ZON 文件现在可以在编译时通过 @import("foo.zon") 导入,前提是结果类型已知。 + +## Zig 0.14.0 版本在编译器后端和编译速度方面有哪些进展? + +该版本在编译器后端和编译速度方面取得了进展: + +- **Multithreaded Backend Support:** 部分编译器后端,如 x86 Backend,现在支持在单独的线程中运行代码生成,这显著提高了编译速度。 +- **Incremental Compilation:** 引入了增量编译特性,可以通过 -fincremental 标志启用。尽管尚未默认启用,但结合文件系统监听,可以显著缩短修改代码后的重新分析时间,提供快速的编译错误反馈。 +- **x86 Backend:** x86 后端在行为测试套件中的通过率已接近 LLVM 后端,并且在开发时通常比 LLVM 后端提供更快的编译速度和更好的调试器支持。虽然尚未默认选中,但鼓励用户尝试使用 -fno-llvm 或在构建脚本中设置 use\_llvm = false 来启用。 + +## Zig 0.14.0 版本在工具链和运行时方面有哪些值得注意的更新? + +该版本在工具链和运行时方面也有多项更新: + +- **UBSan Runtime:** Zig 现在为 UBSan 提供了运行时库,在 Debug 模式下默认启用,可以在 C 代码触发未定义行为时提供详细的错误信息和堆栈跟踪。 +- **LLVM 19:** Zig 已升级到 LLVM 19.1.7 版本。 +- **musl 1.2.5:** 更新了捆绑的 musl 版本,并应用了安全补丁和目标平台特定补丁。 +- **glibc 2.41:** 支持 cross-compiling glibc 2.40 和 2.41 版本,并修复了多个问题,提高了与 glibc 的兼容性。 +- **Linux 6.13.4 Headers:** 包含了 Linux 内核头文件版本 6.13.4。 +- **Darwin libSystem 15.1:** 包含了 Xcode SDK 版本 15.1 的 Darwin libSystem 符号。 +- **MinGW-w64:** 更新了捆绑的 MinGW-w64 版本,并捆绑了 winpthreads 库,支持 cross-compiling 到 thumb-windows-gnu。 +- **wasi-libc:** 更新了捆绑的 wasi-libc 版本。 +- **Optimized memcpy:** 提供了优化的 memcpy 实现,不再捆绑 musl 的 memcpy 文件。 +- **Integrated Fuzzer:** 集成了 alpha 质量的 fuzzer,可以通过 --fuzz CLI 选项使用,并提供一个 fuzzer Web UI 显示实时代码覆盖率。 + +# 总结 + +Zig 0.14.0 版本是向 1.0.0 版本迈进的重要一步,在性能优化(尤其是编译速度)、跨平台支持、语言特性和标准库方面都带来了显著改进。增量编译和快速 x86 后端是关键的长期投资,旨在提升开发者体验。新语言特性如 Labeled Switch 和 Decl Literals 提供了更强大和安全的编程模式。标准库的重组和容器的调整反映了社区的使用模式和最佳实践。构建系统也获得了重要升级,使模块管理和依赖处理更加灵活。尽管仍存在已知 bug,但 Zig 社区在本次发布中展示了活跃的开发和持续的进步。 diff --git a/content/post/2023-09-05-bog-gc-1-en.smd b/content/post/2023-09-05-bog-gc-1-en.smd new file mode 100644 index 0000000..1f3c43b --- /dev/null +++ b/content/post/2023-09-05-bog-gc-1-en.smd @@ -0,0 +1,200 @@ +--- +.title = "Bog GC Design", +.date = @date("2023-09-05T16:40:50+0800"), +.author = "Feng Wenxuan", +.layout = "post.shtml", +.draft = false, +.custom = { + .math = true, + .mermaid = true +}, +--- + +# Bog GC Design + +Bog is a small scripting language developed using Zig. Its GC design is inspired by a paper titled [An efficient of Non-Moving GC for Function languages](https://www.pllab.riec.tohoku.ac.jp/papers/icfp2011UenoOhoriOtomoAuthorVersion.pdf). + +## Overview + +1. Introduction + - Design of the Heap + - Types of GC + - Design of Bitmap +2. Implementation + +## Introduction + +GC stands for garbage collection, which is primarily a memory management strategy for the `heap` region. Memory allocations in the heap are done in exponentially increasing sizes, with a special sub-heap dedicated solely to very large objects. One advantage of this approach might be its efficiency in handling memory requests of various sizes. + +Represented as a formula: + +$$ +Heap = (M, S, (H_3, H_4, ..., H_{12})) +$$ + +Where: + +1. M: is a special region dedicated to storing large objects. +2. S: represents the free space. +3. H: is the sub-heap used for storing smaller objects. $H_i$ represents the sub-heap of size $2^i$, where each addition is twice the size of the previous. + +```=html +
+graph TD
+  A[Heap]
+  B[M]
+  C[S]
+  D[H:Sub Heap]
+
+  A --> B
+  A --> C
+  A --> D
+  D --> H3
+  D --> H4
+  D --> H5
+  D --> Hi
+
+``` + +```bash ++------------+ +| Container | +| +------+ | +| |Box 1| | +| +------+ | +| +------+ | +| |Box 2| | +| +------+ | +| +------+ | +| |Box 3| | +| +------+ | ++------------+ +``` + +### Memory Sub-Heap Pool + +We've designed a memory resource pool. This pool consists of numerous allocation segments of fixed size. It means that, regardless of how much space a sub-heap requests, it will request it in units of these fixed-size "segments". For instance, if the allocation segments in the pool are of size 1MB, then a sub-heap might request space in sizes of 1MB, 2MB, 3MB, etc., rather than requesting non-integer multiples like 1.5MB or 2.5MB. + +The dynamic allocation and reclaiming of space by the sub-heaps from a resource pool made up of fixed-size segments provide greater flexibility and may enhance the efficiency of memory utilization. + +### Types of GC + +There are many common types of GC. + +In terms of **bitmap recorded data**, there exists "Generational Garbage Collection". In Generational Garbage Collection, "generations" or "ages" do not refer to the bitmap. They actually denote portions of the memory used to store objects. Based on the lifespan of objects, GC categorizes them into different generations. The fundamental idea behind this strategy is that newly created objects will become garbage sooner, whereas older objects might live longer. + +Generally, in Generational GC, there are two primary generations: + +1. **Young Generation**: Newly created objects are initially placed here. The Young Generation space is usually smaller and is garbage collected frequently. + +2. **Old or Tenured Generation**: After objects have lived in the Young Generation for a sufficient amount of time and have survived several garbage collections, they are moved to the Old Generation. The Old Generation space is typically larger than the Young Generation, and garbage collection occurs less frequently since objects in the Old Generation are expected to have a longer lifespan. + +Bitmaps serve as a tool here, used to track and manage which objects in each generation are active (i.e., still in use) and which are garbage. When the algorithm extends to Generational GC, multiple bitmaps can be maintained for different generations within the same heap space. In this way, active and inactive objects of each generation can be tracked individually. + +"Maintain one bitmap for the Young Generation and another for the Old Generation." This allows us to consider each generation separately during garbage collection, optimizing the efficiency and performance of the collection process. + +In terms of **moving existing data**, there are "Moving GC" and "Non-Moving GC". Moving GC relocates living objects to new memory addresses and compresses memory, while Non-Moving GC doesn't move living objects, allowing other languages to seamlessly call data in memory. + +Within this Moving GC category, there are "Generational Copying Collectors" and "Cheney's Copying Collector". + +Generational Copying Collector: It is a common method of garbage collection, especially in functional programming languages. It assumes that newly created objects will soon become unreachable (i.e., "die"), whereas older objects are more likely to persist. Thus, memory is divided into two or more "generations". New objects are created in the "Young Generation", and when they live long enough, they are moved to the "Old Generation". + +Cheney's Copying Collector: This is a garbage collector used for semi-space. It works by dividing the available memory in half and only allocating objects in one half. When this half is exhausted, the collector performs garbage collection by copying active objects to the other half. The original half is then completely emptied, becoming the new available space. Cheney's collector is particularly suited for handling data with short lifespans because it quickly copies only the active data while ignoring the dead data. This makes it highly efficient for its "minor collections" (collections that only reclaim Young Generation data) when dealing with programs that handle a large amount of short-lifetime data, such as functional programs. **The advantage of this method is that it can efficiently handle memory fragmentation since memory becomes continuously occupied by copying active objects to new locations.** + +Characteristics: + +- **Any precise copy gc requires the runtime system to locate and update all pointers of every heap-allocated data.** +- In traditional garbage collection strategies (Moving GC), compaction is a commonly used technique, moving active objects into a contiguous region of memory, thereby freeing up unused memory. In other words, it consolidates memory fragmentation. + +In Non-Moving GC, there's "Mark-Sweep". + +**The absence of compaction and object movement is very important**, as the value of a pointer (i.e., the memory address of an object) remains fixed and there's no time spent updating moved addresses. This makes Non-Moving GC highly suitable for languages that need to interact with other languages, as they can access objects in memory without the need for extra work. Moreover, the feature of not moving objects is beneficial for supporting multiple native threads. In a multi-threaded environment, if the location of an object in memory keeps shifting, coordination and synchronization between threads become more complicated. Therefore, avoiding object movement simplifies multithreaded programming. + +The benefits are as follows: + +1. **Lock Simplification**: In a multi-threaded environment, if an object needs to be moved (e.g., during the compaction phase of garbage collection), we need to ensure other threads cannot access this object while it's moving. This might require complex locking strategies and synchronization mechanisms. However, if objects never move, this synchronization need is reduced, making locking strategies simpler. + +2. **Pointer Stability**: In multi-threaded programs, threads might share pointers or references to objects. If an object moves in memory, all threads sharing that object would need to update their pointers or references. This not only adds synchronization complexity but might also introduce errors, like dangling pointers. If objects don't move, these pointers remain consistently valid. + +3. **Predictability and Performance**: Not having to move objects means memory access patterns are more stable and predictable. In multi-threaded programs, predictability is a valuable trait as it can reduce contention between threads, improving overall program performance. + +4. **Reduced Pause Times**: Object movement in garbage collection can lead to noticeable pauses in an application because all threads must be paused to move objects safely. In a multi-threaded environment, this pause might be more pronounced as more threads might actively use objects. Not moving objects reduces such pauses. + +5. **Interoperability with Other Languages or Systems**: If your multi-threaded application interoperates with other languages (like C or C++) or systems, having objects in stable locations becomes even more crucial since external code might rely on the fact that objects aren't moving. + +However, Non-Moving GC has its disadvantages: + +1. **Memory Fragmentation**: Since objects don't move, spaces in memory might become non-contiguous. This could lead to memory fragmentation, decreasing memory usage efficiency. + +2. **Memory Allocation**: Due to fragmentation, memory allocation can become more complicated. For instance, if there isn't enough contiguous space to meet an allocation request, the allocator might need to do more work to find available space. This might decrease allocation performance. + +3. **Memory Usage**: Due to fragmentation, memory usage can become less efficient. For instance, if there's a large object without enough contiguous space to store it, it might get split into multiple fragments which could be assigned to different contiguous spaces, potentially decreasing memory utilization. + +4. **Memory Overhead**: Due to fragmentation, memory overhead can become less efficient. For instance, if there's a large object without enough contiguous space to store it, it might get split into multiple fragments which could be assigned to different contiguous spaces, potentially decreasing memory utilization. + +…… + +To address these problems requires many complicated steps, which won't be elaborated on here. We'll focus on Bog's GC for the explanation. + +### Meta Bitmap + +"Meta Bitmap" or "meta-level bitmaps". This is a higher-level bitmap that summarizes the contents of the original bitmap. This hierarchical structure is similar to the inode mapping in file systems or the use of multi-level page tables in computer memory management. + +For instance, consider a simple bitmap: `1100 1100`. A meta-level bitmap might represent how many free blocks are in every 4 bits. In this scenario, the meta-level bitmap could be `1021` (indicating there's 1 free block in the first 4 bits, 2 free blocks in the second 4 bits, and so on). + +The system doesn't just blindly start searching from the beginning of the bitmap for a free bit; it remembers the last-found position. This way, the next search can begin from this position, speeding up the search process further. This means that the time needed to find the next free bit remains approximately the same, regardless of memory size, which is a very efficient performance characteristic. + +What about the worst-case scenario? + +Let's design for a 32-bit architecture. A 32-bit architecture means that the computer's instruction set and data path are designed to handle data units 32 bits wide. Therefore, when operating on 32-bit data units (like an integer or part of a bitmap), such an architecture can typically process all 32 bits at once. This results in logarithmic operations based on 32, because for larger data sections (like a bitmap), operations might need to proceed in blocks/chunks of 32 bits. **The search time is logarithmically related to the size of segmentSize**. + +For example, if a bitmap is 320 bits long, then on a 32-bit architecture, the worst-case scenario might require checking 10 blocks of 32 bits to find a free bit. This can be represented by log32(320), which results in 10. + +### Bitmap + +Since Bog's GC is essentially still based on "Mark-Sweep", using bitmaps to record data is indispensable. In Bog, we adopted the method of "bitmap records data" for GC. And to improve efficiency, we introduced the concept of meta-bitmaps, where every 4 elements correspond to a meta-bitmap, recording the occupancy status of multiple spaces, and increasing the depth based on the object age in the heap. + +### Implementation + +In reality, Bog's design is a bit more complex. Here are sample in practical code: + +```zig +const Page = struct { + const max_size = 1_048_576; + comptime { + // 2^20, 1MiB + assert(@sizeOf(Page) == max_size); + } + const val_count = @divFloor(max_size - @sizeOf(u32) * 2, (@sizeOf(Value) + @sizeOf(State))); + const pad_size = max_size - @sizeOf(u32) * 2 - (@sizeOf(Value) + @sizeOf(State)) * val_count; + ... +} +``` + +1. `max_size`: Represents the maximum number of bytes a Page can store. We have defined a constant to represent the size of 1 MiB and ensure at compile time that the size of the Page type is exactly 1 MiB. Otherwise, a compile-time error will be triggered. +2. `val_count`: Represents the number of Value objects a Page can store. +3. `pad_size`: Represents the size of the unused space remaining in the Page after storing the maximum number of Value objects. + +```zig + const State = enum { + empty, + white, + gray, + black, + }; + const List = std.ArrayListUnmanaged(*Page); + meta: [val_count]State, + __padding: [pad_size]u8, + free: u32, + marked: u32, + values: [val_count]Value, +``` + +1. An enumeration type named `State` is defined, which has four possible values: empty, white, gray, and black. + - In the context of Garbage Collection (GC), these states are typically related to the status of objects during the GC process. For instance, in generational garbage collection, an object might be marked as "white" (unvisited/pending), "gray" (visited but its references not yet processed), or "black" (processed). +2. `List`: Stores pointers of the Page type. +3. `meta`: Represents the state of each Value object within a Page. Here, we use an enum type to represent the state, and since there are only 4 states, they can be represented using 2 bits. Thus, we can use a u32 to represent the state of all Value objects within a Page. Each State potentially corresponds to the status of a Value object in the `values` field. +4. A `__padding` field, used to pad extra memory space. Its size is determined by the previously mentioned `pad_size` and is an array of bytes (u8). This is commonly used to ensure memory alignment of data structures. +5. `free`: Represents the number, index, or other information related to free or available spaces concerning memory management. +6. `marked`: Represents the number, index, or other information about marked spaces, used during the garbage collection process to determine whether to continue checking values on this page. +7. `values`: Represents the Value objects in a Page. It's an array of Value objects, the size of which is determined by `val_count`. diff --git a/content/post/2023-09-05-bog-gc-1.smd b/content/post/2023-09-05-bog-gc-1.smd new file mode 100644 index 0000000..227b82a --- /dev/null +++ b/content/post/2023-09-05-bog-gc-1.smd @@ -0,0 +1,194 @@ +--- +.title = "Bog GC 设计 -- 概念篇", +.date = @date("2023-09-05T16:38:36+0800"), +.author = "文轩", +.layout = "post.shtml", +.draft = false, +.custom = { + .math = true, // TODO: mathjax + .mermaid = true, +}, +--- + +[Bog](https://github.com/Vexu/bog) 是一款基于 Zig 开发的小型脚本语言。它的 GC 设计受到一篇论文[An efficient of Non-Moving GC for Function languages](https://www.pllab.riec.tohoku.ac.jp/papers/icfp2011UenoOhoriOtomoAuthorVersion.pdf)的启发。 + +## 梗概 + +1. 概述 + - Heap 的设计 + - GC 的类别 + - Bitmap 的设计 +2. 实现 + +## 概述 + +GC 是一种垃圾回收的机制,主要是针对`heap`区域的内存管理策略。在堆中的内存分配是按照指数级增长的大小进行的,此外还有一个专门用于非常大对象的特殊子堆。这种方法的一个优点可能是它可以高效地处理各种大小的内存请求。 + +以公式来进行表示: + +$$ +Heap = (M, S, (H_3, H_4, ..., H_{12})) +$$ + +其中: + +1. M 是特殊的区域,用于存储大对象 +2. S 是空闲区域 +3. H 是子堆,用于存储小对象,$H_i$表示大小为$2^i$的子堆,每次新增都为之前的两倍 + +```=html +
+graph TD
+  A[Heap]
+  B[M]
+  C[S]
+  D[H:Sub Heap]
+
+  A --> B
+  A --> C
+  A --> D
+  D --> H3
+  D --> H4
+  D --> H5
+  D --> Hi
+
+``` + +```bash ++------------+ +| Container | +| +------+ | +| |Box 1| | +| +------+ | +| +------+ | +| |Box 2| | +| +------+ | +| +------+ | +| |Box 3| | +| +------+ | ++------------+ +``` + +### 内存子堆池 + +我们设计一个内存的资源池。这个池由许多固定大小的分配段组成。这意味着,无论子堆请求多少空间,它都会以这些固定大小的“段”为单位来请求。例如,如果池中的分配段大小为 1MB,那么一个子堆可能会请求 1MB、2MB、3MB 等大小的空间,而不是请求 1.5MB 或 2.5MB 这样的非整数倍的大小。 + +而子堆从一个由固定大小的段组成的资源池中动态分配和回收空间,这种策略可以提供更高的灵活性,并可能提高内存使用的效率。 + +### GC 的类别 + +常见的 GC 有很多类型。 + +以**位图记录数据**来说,有“代际垃圾收集 GC”。在代际垃圾收集(Generational Garbage Collection)中,"代"或"世代"并不是指位图。它们实际上是指内存中的一部分,用于存储对象。基于对象的生存周期,GC 把它们分为不同的代。这种策略背后的基本思想是,新创建的对象很快就会变为垃圾,而旧对象则可能存活得更久。 + +一般来说,在代际 GC 中,有两个主要的代: + +1. **新生代(Young Generation)**:新创建的对象首先被放置在这里。新生代空间通常较小,并且经常进行垃圾收集。 + +2. **老年代(Old or Tenured Generation)**:当对象在新生代中存活了足够长的时间并经历了多次垃圾收集后,它们会被移动到老年代。老年代空间通常比新生代大,并且垃圾收集的频率较低,因为预期老年代中的对象会有更长的生命周期。 + +位图在这里是一个工具,用于跟踪和管理每一代中哪些对象是活跃的(即仍在使用中)和哪些是垃圾。当算法扩展为代际 GC 时,可以为同一个堆空间的不同代维护多个位图。这样,每个代的活动和非活动对象都可以被单独地跟踪。 + +“为新生代维护一个位图,为老年代维护另一个位图”。这使得在进行垃圾收集时,我们可以单独地考虑每一个代,从而优化垃圾收集的效率和性能。 + +以是否**移动旧有的数据**来说,有“移动 GC”和“非移动 GC”。移动 GC 会将存活的对象移动到新的内存地址、压缩内存,而非移动 GC 则不会移动存活的对象,可以无缝由其他语言来调用内存里的数据。 + +在这种移动 GC 中,有“分代复制收集器”和“Cheney 复制收集器”。 + +分代复制收集器(Generational Copying Collector): 是垃圾收集的一种常见方法,特别是在函数式编程语言中。它假设新创建的对象很快就会变得不可达(即“死亡”),而老的对象则更可能持续存在。因此,内存被分成两个或更多的“代”,新对象在“新生代”中创建,当它们存活足够长的时间时,它们会被移到“老生代”。 + +Cheney 的拷贝收集器: 是一种用于半区(semi-space)的拷贝垃圾收集器。它的工作原理是将可用内存分为两半,并且只在其中一半中分配对象。当这一半用完时,收集器通过拷贝活跃对象到另一半空间来进行垃圾收集。然后,原先的那一半空间就完全清空,成为新的可用空间。Cheney 的收集器特别适用于处理短生命周期的数据,因为它可以快速地只拷贝活跃的数据,而忽略死亡的数据。这使得它在处理大量短生命周期数据的程序(例如函数式程序)时,对于其“次要收集”(minor collection,即仅仅回收新生代数据的收集)非常高效。**这种方法的优势在于它可以有效地处理内存碎片,因为通过复制活动对象到新位置,内存会被连续地占用。** + +特点: + +- **任何精确的 copy gc 都需要 runtime 系统定位和更新每个堆分配数据的所有指针** +- 在传统的垃圾收集策略(移动 GC)中,压缩是一种常用的技术,它会将活跃的对象移到内存的一个连续区域中,从而释放出未使用的内存。换言之,就是整理内存碎片。 + +在非移动 GC 中,有“标记-清除”。 + +**不需要进行压缩和对象移动这一特性是非常重要**,指针的值(即对象的内存地址)是固定的,也不需要花时间更新移动的地址。这使得非移动 GC 非常适合于需要与其他语言进行交互的语言,因为它们可以在不需要额外的工作的情况下访问内存中的对象。而且不需要移动对象的这一特性对于支持多原生线程也是有益的。在多线程环境中,如果对象在内存中的位置不断移动,那么线程之间的协调和同步将变得更加复杂。因此,避免对象移动可以简化多线程编程。 + +有益的原因如下: + +1. **锁的简化**: 在一个多线程环境中,如果对象需要移动(例如,在垃圾收集的压缩阶段),那么我们需要确保其他线程在对象移动时不能访问这个对象。这可能需要使用复杂的锁策略和同步机制。但是,如果对象永远不移动,我们就可以减少这种同步的需求,使得锁策略更简单。 + +2. **指针的稳定性**: 在多线程程序中,线程之间可能会共享指向对象的指针或引用。如果对象在内存中移动了,那么所有共享该对象的线程都需要更新其指针或引用。这不仅会增加同步的复杂性,而且可能会引入错误,如野指针。如果对象不移动,这些指针就会始终有效。 + +3. **预测性和性能**: 不需要移动对象意味着内存访问模式更加稳定和可预测。在多线程程序中,预测性是一个宝贵的特性,因为它可以减少线程之间的争用,从而提高程序的整体性能。 + +4. **减少暂停时间**: 垃圾收集中的对象移动可能导致应用程序的明显暂停,因为必须暂停所有线程来安全地进行移动。在多线程环境中,这种暂停可能更加明显,因为有更多的线程可能正在活跃地使用对象。不移动对象可以减少这种暂停。 + +5. **与其他语言或系统的互操作**: 如果您的多线程应用程序与其他语言(如 C 或 C++)或系统进行互操作,那么对象的稳定位置将更加重要,因为外部代码可能依赖于对象不移动的事实。 + +但同样的,非移动 GC 也有缺点: + +1. **内存碎片**: 由于对象不会移动,因此内存中的空间可能会变得不连续。这可能会导致内存碎片,从而降低内存使用效率。 +2. **内存分配**: 由于内存碎片,内存分配可能会变得更加复杂。例如,如果没有足够的连续空间来满足分配请求,那么分配器可能需要进行更多的工作来查找可用的空间。这可能会导致分配的性能下降。 +3. **内存使用**: 由于内存碎片,内存使用可能会变得更加低效。例如,如果有一个大对象,但是没有足够的连续空间来存储它,那么它可能会被分成多个碎片,这些碎片可能会被分配给不同的连续空间。这可能会导致内存使用率降低。 +4. **内存占用**: 由于内存碎片,内存占用可能会变得更加低效。例如,如果有一个大对象,但是没有足够的连续空间来存储它,那么它可能会被分成多个碎片,这些碎片可能会被分配给不同的连续空间。这可能会导致内存使用率降低。 + +为了解决这些问题需要很多复杂的步骤,在此不多赘述。单以 Bog 的 GC 来讲解。 + +### Meta Bitmap + +“元级位图”或“meta-level bitmaps”。这是一个更高级别的位图,用于汇总原始位图的内容。这种层次化的结构类似于文件系统中的 inode 映射或多级页表在计算机内存管理中的使用。 + +例如,考虑一个简单的位图:`1100 1100`。一个元级位图可能表示每 4 位中有多少个空闲块。在这种情况下,元级位图可能是 `1021`(表示第一个 4 位中有 1 个空闲块,第二个 4 位中有 2 个空闲块,以此类推)。 + +系统不仅仅是盲目地从位图的开始处查找空闲位,而是记住上一次查找到的位置。这样,下次查找可以从这个位置开始,进一步加速查找过程。这意味着无论内存大小如何,找到下一个空闲位所需的时间都大致相同,这是一个非常高效的性能特性。 + +如果是最坏的情况呢? + +以 32 位架构来设计。32 位架构意味着计算机的指令集和数据路径设计为处理 32 位宽的数据单元。因此,当操作 32 位数据单元(例如一个整数或位图的一部分)时,这样的架构通常可以一次性处理所有 32 位。这导致以 32 为底的对数操作,因为对于较大的数据段(如一个位图),操作可能需要按 32 位的块/段进行。**查找时间与 segmentSize 的大小成对数关系**。 + +例如,如果一个位图有 320 位,那么在 32 位架构上,最坏的情况可能需要检查 10 个 32 位块才能找到一个空闲位。这可以通过 $\log_{32}(320)$来表示,结果是 10。 + +### Bitmap + +由于 Bog 的 GC 本质上还是采用了“标记-清除”,所以利用位图来记录数据是必不可少的。在 Bog 中,我们采用了“位图记录数据”的方式来进行 GC。而为了提高效率,我们增加了元位图的概念,即每 4 个元素对应一个元位图,用于记录多空间的占用状态,并且根据 heap 的对象时间增加深度。 + +### 实现 + +实际上,在 Bog 的设计中,要更加复杂一些。我们增加了 + +```zig +const Page = struct { + const max_size = 1_048_576; + comptime { + // 2^20, 1MiB + assert(@sizeOf(Page) == max_size); + } + const val_count = @divFloor(max_size - @sizeOf(u32) * 2, (@sizeOf(Value) + @sizeOf(State))); + const pad_size = max_size - @sizeOf(u32) * 2 - (@sizeOf(Value) + @sizeOf(State)) * val_count; + ... +} +``` + +1. `max_size`: 表示一个 Page 能够存储的最大字节数。我们定义了一个常量来表示 1 MiB 的大小,并在编译时确保 Page 类型的大小恰好为 1 MiB。否则将会触发编译时错误。 +2. `val_count`: 表示一个 Page 能够存储的 Value 对象的数量 +3. `pad_size`: 表示在存储了最大数量的 Value 对象后,Page 剩余的未使用的空间大小 + +```zig + + const State = enum { + empty, + white, + gray, + black, + }; + const List = std.ArrayListUnmanaged(*Page); + meta: [val_count]State, + __padding: [pad_size]u8, + free: u32, + marked: u32, + values: [val_count]Value, +``` + +1. 定义了一个名为 State 的枚举类型,它有四个可能的值:empty、white、gray 和 black。 + - 在垃圾收集(GC)的上下文中,这些状态通常与对象在 GC 过程中的状态有关。例如,在分代垃圾收集中,对象可能会被标记为 "white"(未访问/待处理),"gray"(已访问但其引用尚未处理)或 "black"(已处理)。 +2. `List`: 存储 Page 类型的指针 +3. `meta`: 表示一个 Page 中每个 Value 对象的状态。这里我们使用了一个枚举类型来表示状态,因为状态只有 4 种,所以可以用 2 位来表示。因此,我们可以使用一个 u32 来表示一个 Page 中所有 Value 对象的状态。这样,我们就可以使用一个 u32 来表示一个 Page 中所有 Value 对象的状态。每一个 State 可能对应一个在 values 字段中的 Value 对象的状态。 +4. `__padding` 的字段,用于填充额外的内存空间。它的大小由之前提到的 pad_size 决定,且是一个字节(u8)数组。这通常用于确保数据结构的内存对齐。 +5. `free`: 表示空闲或可用的空间数量、索引或其他与内存管理相关的信息 +6. `marked`: 表示已标记的空间数量、索引或其他与内存管理相关的信息,在垃圾收集的过程中用于检测是否应继续在此页面中检查值 +7. `values`: 表示一个 Page 中的 Value 对象。它是一个 Value 对象的数组,其大小由 val_count 决定。 diff --git a/content/post/2023-09-05-hello-world.smd b/content/post/2023-09-05-hello-world.smd new file mode 100644 index 0000000..110d1c8 --- /dev/null +++ b/content/post/2023-09-05-hello-world.smd @@ -0,0 +1,30 @@ +--- +.title = "欢迎 Zig 爱好者向本网站供稿", +.date = @date("2023-09-05T16:13:13+0800"), +.author = "刘家财", +.layout = "post.shtml", +.draft = false, +--- + +欢迎社区用户向 ZigCC 供稿(关于 Zig 的任何话题),方便与社区更多人分享。文章会发布在: + +- [ZigCC 网站](https://ziglang.cc) +- [ZigCC 公众号](https://github.com/zigcc/.github/raw/main/zig_mp.png) + +# 供稿方式 + +1. Fork 仓库 https://github.com/zigcc/zigcc.github.io +2. 在 `content/post` 内添加自己的文章(md 或 org 格式均可),文件命名为: `${YYYY}-${MM}-${DD}-${SLUG}.md` +3. 文件开始需要包含一些描述信息,例如[本文件](https://github.com/zigcc/zigcc.github.io/tree/main/content/post/2023-09-05-hello-world.md)中的: + +``` +--- +title: 欢迎 Zig 爱好者向本网站供稿 +author: 刘家财 +date: '2023-09-05T16:13:13+0800' +--- +``` + +## 本地预览 + +在写完文章后,可以使用 [Hugo](https://gohugo.io/) 进行本地预览,只需在项目根目录执行 `hugo server`,这会启动一个 HTTP 服务,默认的访问地址是: http://localhost:1313/ diff --git a/content/post/2023-09-21-zig-midi.smd b/content/post/2023-09-21-zig-midi.smd new file mode 100644 index 0000000..84d58c2 --- /dev/null +++ b/content/post/2023-09-21-zig-midi.smd @@ -0,0 +1,988 @@ +--- +.title = "Zig音频之MIDI —— 源码解读", +.date = @date("2023-09-21T23:15:02+0800"), +.author = "文轩", +.layout = "post.shtml", +.draft = false, +--- + +MIDI 是“乐器数字接口”的缩写,是一种用于音乐设备之间通信的协议。而 [zig-midi](https://github.com/Hejsil/zig-midi) 主要是在对 MIDI 的元数据、音频头等元数据进行一些处理的方法上进行了集成。 + +```bash +. +├── LICENSE +├── ReadMe.md +├── build.zig +├── example +│ └── midi_file_to_text_stream.zig +├── midi +│ ├── decode.zig +│ ├── encode.zig +│ ├── file.zig +│ └── test.zig +├── midi.zig +``` + +## 基础 + +在 MIDI 协议中,`0xFF` 是一个特定的状态字节,用来表示元事件(Meta Event)的开始。元事件是 MIDI 文件结构中的一种特定消息,通常不用于实时音频播放,但它们包含有关 MIDI 序列的元数据,例如序列名称、版权信息、歌词、时间标记、速度(BPM)更改等。 + +以下是一些常见的元事件类型及其关联的 `0xFF` 后的字节: + +- `0x00`: 序列号 (Sequence Number) +- `0x01`: 文本事件 (Text Event) +- `0x02`: 版权通知 (Copyright Notice) +- `0x03`: 序列/曲目名称 (Sequence/Track Name) +- `0x04`: 乐器名称 (Instrument Name) +- `0x05`: 歌词 (Lyric) +- `0x06`: 标记 (Marker) +- `0x07`: 注释 (Cue Point) +- `0x20`: MIDI Channel Prefix +- `0x21`: End of Track (通常跟随值`0x00`,表示轨道的结束) +- `0x2F`: Set Tempo (设定速度,即每分钟的四分音符数) +- `0x51`: SMPTE Offset +- `0x54`: 拍号 (Time Signature) +- `0x58`: 调号 (Key Signature) +- `0x59`: Sequencer-Specific Meta-event + +例如,当解析 MIDI 文件时,如果遇到字节 `0xFF 0x03`,那么接下来的字节将表示序列或曲目名称。 + +在实际的 MIDI 文件中,元事件的具体结构是这样的: + +1. `0xFF`: 元事件状态字节。 +2. 元事件类型字节,例如上面列出的 `0x00`, `0x01` 等。 +3. 长度字节(或一系列字节),表示该事件数据的长度。 +4. 事件数据本身。 + +元事件主要存在于 MIDI 文件中,特别是在标准 MIDI 文件 (SMF) 的上下文中。在实时 MIDI 通信中,元事件通常不会被发送,因为它们通常不会影响音乐的实际播放。 + +## Midi.zig + +本文件主要是处理 MIDI 消息的模块,为处理 MIDI 消息提供了基础结构和函数。 + +```zig +const std = @import("std"); + +const mem = std.mem; + +const midi = @This(); + +pub const decode = @import("midi/decode.zig"); +pub const encode = @import("midi/encode.zig"); +pub const file = @import("midi/file.zig"); + +pub const File = file.File; + +test "midi" { + _ = @import("midi/test.zig"); + _ = decode; + _ = file; +} + +pub const Message = struct { + status: u7, + values: [2]u7, + + pub fn kind(message: Message) Kind { + const _kind = @as(u3, @truncate(message.status >> 4)); + const _channel = @as(u4, @truncate(message.status)); + return switch (_kind) { + 0x0 => Kind.NoteOff, + 0x1 => Kind.NoteOn, + 0x2 => Kind.PolyphonicKeyPressure, + 0x3 => Kind.ControlChange, + 0x4 => Kind.ProgramChange, + 0x5 => Kind.ChannelPressure, + 0x6 => Kind.PitchBendChange, + 0x7 => switch (_channel) { + 0x0 => Kind.ExclusiveStart, + 0x1 => Kind.MidiTimeCodeQuarterFrame, + 0x2 => Kind.SongPositionPointer, + 0x3 => Kind.SongSelect, + 0x6 => Kind.TuneRequest, + 0x7 => Kind.ExclusiveEnd, + 0x8 => Kind.TimingClock, + 0xA => Kind.Start, + 0xB => Kind.Continue, + 0xC => Kind.Stop, + 0xE => Kind.ActiveSensing, + 0xF => Kind.Reset, + + 0x4, 0x5, 0x9, 0xD => Kind.Undefined, + }, + }; + } + + pub fn channel(message: Message) ?u4 { + const _kind = message.kind(); + const _channel = @as(u4, @truncate(message.status)); + switch (_kind) { + // Channel events + .NoteOff, + .NoteOn, + .PolyphonicKeyPressure, + .ControlChange, + .ProgramChange, + .ChannelPressure, + .PitchBendChange, + => return _channel, + + // System events + .ExclusiveStart, + .MidiTimeCodeQuarterFrame, + .SongPositionPointer, + .SongSelect, + .TuneRequest, + .ExclusiveEnd, + .TimingClock, + .Start, + .Continue, + .Stop, + .ActiveSensing, + .Reset, + => return null, + + .Undefined => return null, + } + } + + pub fn value(message: Message) u14 { + // TODO: Is this the right order according to the midi spec? + return @as(u14, message.values[0]) << 7 | message.values[1]; + } + + pub fn setValue(message: *Message, v: u14) void { + message.values = .{ + @as(u7, @truncate(v >> 7)), + @as(u7, @truncate(v)), + }; + } + + pub const Kind = enum { + // Channel events + NoteOff, + NoteOn, + PolyphonicKeyPressure, + ControlChange, + ProgramChange, + ChannelPressure, + PitchBendChange, + + // System events + ExclusiveStart, + MidiTimeCodeQuarterFrame, + SongPositionPointer, + SongSelect, + TuneRequest, + ExclusiveEnd, + TimingClock, + Start, + Continue, + Stop, + ActiveSensing, + Reset, + + Undefined, + }; +}; +``` + +这定义了一个名为 Message 的公共结构,表示 MIDI 消息,为处理 MIDI 消息提供了基础结构和函数。它包含三个字段:状态、值和几个公共方法。 + +- kind 函数:根据 MIDI 消息的状态码确定消息的种类。 +- channel 函数:根据消息的种类返回 MIDI 通道,如果消息不包含通道信息则返回 null。 +- value 和 setValue 函数:用于获取和设置 MIDI 消息的值字段。 +- Kind 枚举:定义了 MIDI 消息的所有可能种类,包括通道事件和系统事件。 + +### midi 消息结构 + +我们需要先了解 MIDI 消息的一些背景。 + +在 MIDI 协议中,某些消息的值可以跨越两个 7 位的字节,这是因为 MIDI 协议不使用每个字节的最高位(这通常被称为状态位)。这意味着每个字节只使用它的低 7 位来携带数据。因此,当需要发送一个大于 7 位的值时(比如 14 位),它会被拆分成两个 7 位的字节。 + +`setValue` 这个函数做的事情是将一个 14 位的值(`u14`)拆分为两个 7 位的值,并将它们设置到 `message.values` 中。 + +以下是具体步骤的解释: + +1. **获取高 7 位**:`v >> 7` 把 14 位的值右移 7 位,这样我们就得到了高 7 位的值。 +2. **截断并转换**:`@truncate(v >> 7)` 截断高 7 位的值,确保它是 7 位的。`@as(u7, @truncate(v >> 7))` 确保这个值是 `u7` 类型,即一个 7 位的无符号整数。 + +3. **获取低 7 位**:`@truncate(v)` 直接截断原始值,保留低 7 位。 + +4. **设置值**:`message.values = .{ ... }` 将这两个 7 位的值设置到 `message.values` 中。 + +### 事件 + +针对事件,我们看 enum。 + +```zig + pub const Kind = enum { + // Channel events + NoteOff, + NoteOn, + PolyphonicKeyPressure, + ControlChange, + ProgramChange, + ChannelPressure, + PitchBendChange, + + // System events + ExclusiveStart, + MidiTimeCodeQuarterFrame, + SongPositionPointer, + SongSelect, + TuneRequest, + ExclusiveEnd, + TimingClock, + Start, + Continue, + Stop, + ActiveSensing, + Reset, + + Undefined, + }; + +``` + +这段代码定义了一个名为 `Kind` 的公共枚举类型(`enum`),它描述了 MIDI 中可能的事件种类。每个枚举成员都代表 MIDI 协议中的一个特定事件。这些事件分为两大类:频道事件(Channel events)和系统事件(System events)。 + +这个 `Kind` 枚举为处理 MIDI 消息提供了一个结构化的方法,使得在编程时可以清晰地引用特定的 MIDI 事件,而不是依赖于原始的数字或其他编码。 + +以下是对每个枚举成员的简要说明: + +#### 频道事件 (Channel events) + +1. **NoteOff**:这是一个音符结束事件,表示某个音符不再播放。 +2. **NoteOn**:这是一个音符开始事件,表示开始播放某个音符。 +3. **PolyphonicKeyPressure**:多声道键盘压力事件,表示对特定音符的压力或触摸敏感度的变化。 +4. **ControlChange**:控制变更事件,用于发送如音量、平衡等控制信号。 +5. **ProgramChange**:程序(音色)变更事件,用于改变乐器的音色。 +6. **ChannelPressure**:频道压力事件,与多声道键盘压力相似,但它适用于整个频道,而不是特定音符。 +7. **PitchBendChange**:音高弯曲变更事件,表示音符音高的上升或下降。 + +#### 系统事件 (System events) + +1. **ExclusiveStart**:独占开始事件,标志着一个独占消息序列的开始。 +2. **MidiTimeCodeQuarterFrame**:MIDI 时间码四分之一帧,用于同步与其他设备。 +3. **SongPositionPointer**:歌曲位置指针,指示序列器的当前播放位置。 +4. **SongSelect**:歌曲选择事件,用于选择特定的歌曲或序列。 +5. **TuneRequest**:调音请求事件,指示设备应进行自我调音。 +6. **ExclusiveEnd**:独占结束事件,标志着一个独占消息序列的结束。 +7. **TimingClock**:计时时钟事件,用于节奏的同步。 +8. **Start**:开始事件,用于启动序列播放。 +9. **Continue**:继续事件,用于继续暂停的序列播放。 +10. **Stop**:停止事件,用于停止序列播放。 +11. **ActiveSensing**:活动感知事件,是一种心跳信号,表示设备仍然在线并工作。 +12. **Reset**:重置事件,用于将设备重置为其初始状态。 + +#### 其他 + +1. **Undefined**:未定义事件,可能表示一个未在此枚举中定义的或无效的 MIDI 事件。 + +## decode.zig + +本文件是对 MIDI 文件的解码器, 提供了一组工具,可以从不同的输入源解析 MIDI 文件的各个部分。这样可以方便地读取和处理 MIDI 文件。 + +```zig +const midi = @import("../midi.zig"); +const std = @import("std"); + +const debug = std.debug; +const io = std.io; +const math = std.math; +const mem = std.mem; + +const decode = @This(); + +fn statusByte(b: u8) ?u7 { + if (@as(u1, @truncate(b >> 7)) != 0) + return @as(u7, @truncate(b)); + + return null; +} + +fn readDataByte(reader: anytype) !u7 { + return math.cast(u7, try reader.readByte()) catch return error.InvalidDataByte; +} + +pub fn message(reader: anytype, last_message: ?midi.Message) !midi.Message { + var first_byte: ?u8 = try reader.readByte(); + const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { + first_byte = null; + break :blk status_byte; + } else if (last_message) |m| blk: { + if (m.channel() == null) + return error.InvalidMessage; + + break :blk m.status; + } else return error.InvalidMessage; + + const kind = @as(u3, @truncate(status_byte >> 4)); + const channel = @as(u4, @truncate(status_byte)); + switch (kind) { + 0x0, 0x1, 0x2, 0x3, 0x6 => return midi.Message{ + .status = status_byte, + .values = [2]u7{ + math.cast(u7, first_byte orelse try reader.readByte()) catch return error.InvalidDataByte, + try readDataByte(reader), + }, + }, + 0x4, 0x5 => return midi.Message{ + .status = status_byte, + .values = [2]u7{ + math.cast(u7, first_byte orelse try reader.readByte()) catch return error.InvalidDataByte, + 0, + }, + }, + 0x7 => { + debug.assert(first_byte == null); + switch (channel) { + 0x0, 0x6, 0x07, 0x8, 0xA, 0xB, 0xC, 0xE, 0xF => return midi.Message{ + .status = status_byte, + .values = [2]u7{ 0, 0 }, + }, + 0x1, 0x3 => return midi.Message{ + .status = status_byte, + .values = [2]u7{ + try readDataByte(reader), + 0, + }, + }, + 0x2 => return midi.Message{ + .status = status_byte, + .values = [2]u7{ + try readDataByte(reader), + try readDataByte(reader), + }, + }, + + // Undefined + 0x4, 0x5, 0x9, 0xD => return midi.Message{ + .status = status_byte, + .values = [2]u7{ 0, 0 }, + }, + } + }, + } +} + +pub fn chunk(reader: anytype) !midi.file.Chunk { + var buf: [8]u8 = undefined; + try reader.readNoEof(&buf); + return decode.chunkFromBytes(buf); +} + +pub fn chunkFromBytes(bytes: [8]u8) midi.file.Chunk { + return midi.file.Chunk{ + .kind = bytes[0..4].*, + .len = mem.readIntBig(u32, bytes[4..8]), + }; +} + +pub fn fileHeader(reader: anytype) !midi.file.Header { + var buf: [14]u8 = undefined; + try reader.readNoEof(&buf); + return decode.fileHeaderFromBytes(buf); +} + +pub fn fileHeaderFromBytes(bytes: [14]u8) !midi.file.Header { + const _chunk = decode.chunkFromBytes(bytes[0..8].*); + if (!mem.eql(u8, &_chunk.kind, midi.file.Chunk.file_header)) + return error.InvalidFileHeader; + if (_chunk.len < midi.file.Header.size) + return error.InvalidFileHeader; + + return midi.file.Header{ + .chunk = _chunk, + .format = mem.readIntBig(u16, bytes[8..10]), + .tracks = mem.readIntBig(u16, bytes[10..12]), + .division = mem.readIntBig(u16, bytes[12..14]), + }; +} + +pub fn int(reader: anytype) !u28 { + var res: u28 = 0; + while (true) { + const b = try reader.readByte(); + const is_last = @as(u1, @truncate(b >> 7)) == 0; + const value = @as(u7, @truncate(b)); + res = try math.mul(u28, res, math.maxInt(u7) + 1); + res = try math.add(u28, res, value); + + if (is_last) + return res; + } +} + +pub fn metaEvent(reader: anytype) !midi.file.MetaEvent { + return midi.file.MetaEvent{ + .kind_byte = try reader.readByte(), + .len = try decode.int(reader), + }; +} + +pub fn trackEvent(reader: anytype, last_event: ?midi.file.TrackEvent) !midi.file.TrackEvent { + var peek_reader = io.peekStream(1, reader); + var in_reader = peek_reader.reader(); + + const delta_time = try decode.int(&in_reader); + const first_byte = try in_reader.readByte(); + if (first_byte == 0xFF) { + return midi.file.TrackEvent{ + .delta_time = delta_time, + .kind = midi.file.TrackEvent.Kind{ .MetaEvent = try decode.metaEvent(&in_reader) }, + }; + } + + const last_midi_event = if (last_event) |e| switch (e.kind) { + .MidiEvent => |m| m, + .MetaEvent => null, + } else null; + + peek_reader.putBackByte(first_byte) catch unreachable; + return midi.file.TrackEvent{ + .delta_time = delta_time, + .kind = midi.file.TrackEvent.Kind{ .MidiEvent = try decode.message(&in_reader, last_midi_event) }, + }; +} + +/// Decodes a midi file from a reader. Caller owns the returned value +/// (see: `midi.File.deinit`). +pub fn file(reader: anytype, allocator: *mem.Allocator) !midi.File { + var chunks = std.ArrayList(midi.File.FileChunk).init(allocator); + errdefer { + (midi.File{ + .format = 0, + .division = 0, + .chunks = chunks.toOwnedSlice(), + }).deinit(allocator); + } + + const header = try decode.fileHeader(reader); + const header_data = try allocator.alloc(u8, header.chunk.len - midi.file.Header.size); + errdefer allocator.free(header_data); + + try reader.readNoEof(header_data); + while (true) { + const c = decode.chunk(reader) catch |err| switch (err) { + error.EndOfStream => break, + else => |e| return e, + }; + + const chunk_bytes = try allocator.alloc(u8, c.len); + errdefer allocator.free(chunk_bytes); + try reader.readNoEof(chunk_bytes); + try chunks.append(.{ + .kind = c.kind, + .bytes = chunk_bytes, + }); + } + + return midi.File{ + .format = header.format, + .division = header.division, + .header_data = header_data, + .chunks = chunks.toOwnedSlice(), + }; +} +``` + +1. statusByte: 解析 MIDI 消息的首个字节,来确定是否这是一个状态字节,还是一个数据字节。将一个字节 b 解码为一个 u7 类型的 MIDI 状态字节,如果字节 b 不是一个状态字节,则返回 null。换句话说,midi 的消息是 14 位,如果高 7 位不为空,则是 midi 消息的状态字节。在 MIDI 协议中,消息的首个字节通常是状态字节,但也可能用之前的状态字节(这称为“运行状态”)来解释接下来的字节。因此,这段代码需要确定它是否读取了一个新的状态字节,或者它是否应该使用前一个消息的状态字节。 +2. readDataByte: 从 reader 中读取并返回一个数据字节。如果读取的字节不符合数据字节的规定,则抛出 InvalidDataByte 错误。 +3. message: 从 reader 读取并解码一个 MIDI 消息。如果读取的字节不能形成一个有效的 MIDI 消息,则抛出 InvalidMessage 错误。这是一个复杂的函数,涉及到解析 MIDI 消息的不同种类。 +4. chunk,chunkFromBytes: 这两个函数从 reader 或直接从字节数组 bytes 中解析一个 MIDI 文件块头。 +5. fileHeader, fileHeaderFromBytes: 这两个函数从 reader 或直接从字节数组 bytes 中解析一个 MIDI 文件头。 +6. int: 从 reader 中解码一个可变长度的整数。 +7. metaEvent: 从 reader 中解析一个 MIDI 元事件。 +8. trackEvent: 从 reader 中解析一个 MIDI 轨道事件。它可以是 MIDI 消息或元事件。 +9. file: 用于从 reader 解码一个完整的 MIDI 文件。它首先解码文件头,然后解码所有的文件块。这个函数会返回一个表示 MIDI 文件的结构体。 + +### message 解析 + +```zig +const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { + first_byte = null; + break :blk status_byte; +} else if (last_message) |m| blk: { + if (m.channel() == null) + return error.InvalidMessage; + + break :blk m.status; +} else return error.InvalidMessage; +``` + +这段代码的目的是确定 MIDI 消息的状态字节。它可以是从 `reader` 读取的当前字节,或者是从前一个 MIDI 消息中获取的。这样做是为了支持 MIDI 协议中的“运行状态”,在该协议中,连续的 MIDI 消息可能不会重复状态字节。 + +1. `const status_byte = ...;`: 这是一个常量声明。`status_byte` 将保存 MIDI 消息的状态字节。 + +2. `if (statusByte(first_byte.?)) |status_byte| blk: { ... }`: + +- `statusByte(first_byte.?)`: 这是一个函数调用,它检查 `first_byte` 是否是一个有效的状态字节。`.?` 是可选值的语法,它用于解包 `first_byte` 的值(它是一个可选的 `u8`,可以是 `u8` 或 `null`)。 +- `|status_byte|`: 如果 `statusByte` 函数返回一个有效的状态字节,则这个值会被捕获并赋给这里的 `status_byte` 变量。 +- `blk:`: 这是一个匿名代码块的标签。Zig 允许你给代码块命名,这样你可以从该代码块中跳出。 +- `{ ... }`: 这是一个代码块。在这里,`first_byte` 被设置为 `null`,然后使用 `break :blk status_byte;` 来结束此代码块,并将 `status_byte` 的值赋给外部的 `status_byte` 常量。 + +3. `else if (last_message) |m| blk: { ... }`: + +- 如果 `first_byte` 不是一个状态字节,代码会检查是否存在一个名为 `last_message` 的前一个 MIDI 消息。 +- `|m|`: 如果 `last_message` 存在(即它不是 `null`),它的值将被捕获并赋给 `m`。 +- `{ ... }`: 这是另一个代码块。在这里,它检查 `m` 是否有一个通道。如果没有,则返回一个 `InvalidMessage` 错误。否则,使用 `break :blk m.status;` 结束此代码块,并将 `m.status` 的值赋给外部的 `status_byte` 常量。 + +4. `else return error.InvalidMessage;`: 如果 `first_byte` 不是状态字节,并且不存在前一个消息,那么返回一个 `InvalidMessage` 错误。 + +## encode.zig + +本文件用于将 MIDI 数据结构编码为其对应的二进制形式。具体来说,它是将内存中的 MIDI 数据结构转换为 MIDI 文件格式的二进制数据。 + +```zig +const midi = @import("../midi.zig"); +const std = @import("std"); + +const debug = std.debug; +const io = std.io; +const math = std.math; +const mem = std.mem; + +const encode = @This(); + +pub fn message(writer: anytype, last_message: ?midi.Message, msg: midi.Message) !void { + if (msg.channel() == null or last_message == null or msg.status != last_message.?.status) { + try writer.writeByte((1 << 7) | @as(u8, msg.status)); + } + + switch (msg.kind()) { + .ExclusiveStart, + .TuneRequest, + .ExclusiveEnd, + .TimingClock, + .Start, + .Continue, + .Stop, + .ActiveSensing, + .Reset, + .Undefined, + => {}, + .ProgramChange, + .ChannelPressure, + .MidiTimeCodeQuarterFrame, + .SongSelect, + => { + try writer.writeByte(msg.values[0]); + }, + .NoteOff, + .NoteOn, + .PolyphonicKeyPressure, + .ControlChange, + .PitchBendChange, + .SongPositionPointer, + => { + try writer.writeByte(msg.values[0]); + try writer.writeByte(msg.values[1]); + }, + } +} + +pub fn chunkToBytes(_chunk: midi.file.Chunk) [8]u8 { + var res: [8]u8 = undefined; + mem.copy(u8, res[0..4], &_chunk.kind); + mem.writeIntBig(u32, res[4..8], _chunk.len); + return res; +} + +pub fn fileHeaderToBytes(header: midi.file.Header) [14]u8 { + var res: [14]u8 = undefined; + mem.copy(u8, res[0..8], &chunkToBytes(header.chunk)); + mem.writeIntBig(u16, res[8..10], header.format); + mem.writeIntBig(u16, res[10..12], header.tracks); + mem.writeIntBig(u16, res[12..14], header.division); + return res; +} + +pub fn int(writer: anytype, i: u28) !void { + var tmp = i; + var is_first = true; + var buf: [4]u8 = undefined; + var fbs = io.fixedBufferStream(&buf).writer(); + + // TODO: Can we find a way to not encode this in reverse order and then flipping the bytes? + while (tmp != 0 or is_first) : (is_first = false) { + fbs.writeByte(@as(u7, @truncate(tmp)) | (@as(u8, 1 << 7) * @intFromBool(!is_first))) catch + unreachable; + tmp >>= 7; + } + mem.reverse(u8, fbs.context.getWritten()); + try writer.writeAll(fbs.context.getWritten()); +} + +pub fn metaEvent(writer: anytype, event: midi.file.MetaEvent) !void { + try writer.writeByte(event.kind_byte); + try int(writer, event.len); +} + +pub fn trackEvent(writer: anytype, last_event: ?midi.file.TrackEvent, event: midi.file.TrackEvent) !void { + const last_midi_event = if (last_event) |e| switch (e.kind) { + .MidiEvent => |m| m, + .MetaEvent => null, + } else null; + + try int(writer, event.delta_time); + switch (event.kind) { + .MetaEvent => |meta| { + try writer.writeByte(0xFF); + try metaEvent(writer, meta); + }, + .MidiEvent => |msg| try message(writer, last_midi_event, msg), + } +} + +pub fn file(writer: anytype, f: midi.File) !void { + try writer.writeAll(&encode.fileHeaderToBytes(.{ + .chunk = .{ + .kind = midi.file.Chunk.file_header.*, + .len = @as(u32, @intCast(midi.file.Header.size + f.header_data.len)), + }, + .format = f.format, + .tracks = @as(u16, @intCast(f.chunks.len)), + .division = f.division, + })); + try writer.writeAll(f.header_data); + + for (f.chunks) |c| { + try writer.writeAll(&encode.chunkToBytes(.{ + .kind = c.kind, + .len = @as(u32, @intCast(c.bytes.len)), + })); + try writer.writeAll(c.bytes); + } +} +``` + +- message 函数:这是将 MIDI 消息编码为字节序列的函数, 将单个 MIDI 消息编码为其二进制形式。根据消息类型,这会向提供的 writer 写入一个或多个字节。若消息需要状态字节,并且不同于前一个消息的状态,函数会写入状态字节。接着,根据消息的种类,函数会写入所需的数据字节。 + +- chunkToBytes 函数:将 MIDI 文件的块(Chunk)信息转换为 8 字节的二进制数据。这 8 字节中包括 4 字节的块类型和 4 字节的块长度。它复制块类型到前 4 个字节,然后写入块的长度到后 4 个字节,并返回结果。 + +- fileHeaderToBytes 函数:编码 MIDI 文件的头部为 14 字节的二进制数据。这 14 字节包括块信息、文件格式、轨道数量和时间划分信息。 + +- int 函数:将一个整数编码为 MIDI 文件中的可变长度整数格式。在 MIDI 文件中,某些整数值使用一种特殊的编码格式,可以根据整数的大小变化长度。 + +- metaEvent 函数:将 MIDI 元事件(Meta Event)编码为二进制数据, 这包括事件的类型和长度。具体则是编码一个元事件,首先写入其种类字节,然后是其长度。 + +- trackEvent 函数:编码轨道事件。轨道事件可以是元事件或 MIDI 事件,函数首先写入事件之间的时间差(delta 时间),然后根据事件类型(MetaEvent 或 MidiEvent)编码事件内容。 + +- file 函数:这是主函数,用于将整个 MIDI 文件数据结构编码为其二进制形式。它首先编码文件头,然后循环编码每个块和块中的事件。 + +### int 函数 + +```zig + +pub fn int(writer: anytype, i: u28) !void { + var tmp = i; + var is_first = true; + var buf: [4]u8 = undefined; + var fbs = io.fixedBufferStream(&buf).writer(); + + // TODO: Can we find a way to not encode this in reverse order and then flipping the bytes? + while (tmp != 0 or is_first) : (is_first = false) { + fbs.writeByte(@as(u7, @truncate(tmp)) | (@as(u8, 1 << 7) * @intFromBool(!is_first))) catch + unreachable; + tmp >>= 7; + } + mem.reverse(u8, fbs.context.getWritten()); + try writer.writeAll(fbs.context.getWritten()); +} +``` + +这个函数`int`用于编码一个整数为 MIDI 文件中的可变长度整数格式。在 MIDI 文件中,许多值(如 delta 时间)使用这种可变长度编码。 + +详细地解析这个函数的每一步: + +1. **参数定义**: + +- `writer`: 任意类型的写入对象,通常是一种流或缓冲区,可以向其写入数据。 +- `i`: 一个最多 28 位的无符号整数(`u28`),即要编码的值。 + +1. **局部变量初始化**: + +- `tmp`:作为输入整数的临时副本。 +- `is_first`:一个布尔值,用于指示当前处理的是否是整数的第一个字节。 +- `buf`: 定义一个 4 字节的缓冲区。因为最大的`u28`值需要 4 个字节的可变长度编码。 +- `fbs`:使用`io.fixedBufferStream`创建一个固定缓冲区的流,并获取它的写入器。 + +1. **循环进行可变长度编码**: + +- 循环条件是:直到`tmp`为 0 并且不是第一个字节。 +- `: (is_first = false)` 是一个后置条件,每次循环结束后都会执行。 +- `(@as(u8, 1 << 7) * @intFromBool(!is_first))` + - `1 << 7`: 这个操作是左移操作。数字 1 在二进制中表示为`0000 0001`。当你将它左移 7 位时,你得到`1000 0000`,这在十进制中等于 `128`。 + - `@intFromBool(!is_first)`: 这是将上一步得到的布尔值转换为整数。在许多编程语言中,true 通常被视为 1,false 被视为 0。在 Zig 中,这种转换不是隐式的,所以需要用`@intFromBool()`函数来进行转换 + - `@as(u8, 1 << 7)`: 这里是将数字 128(从 1 << 7 得到)显式地转换为一个 8 位无符号整数。 + - `(@as(u8, 1 << 7) * @intFromBool(!is_first))`: 将转换后的数字 128 与从布尔转换得到的整数(0 或 1)相乘。如果`is_first`为`true`(即这是第一个字节),那么整个表达式的值为 0。如果`is_first为false`(即这不是第一个字节),那么整个表达式的值为 128(`1000 0000` in 二进制)。 + - 这种结构在 MIDI 变长值的编码中很常见。MIDI 变长值的每个字节的最高位被用作“继续”位,指示是否有更多的字节跟随。如果最高位是 1,那么表示还有更多的字节;如果是 0,表示这是最后一个字节。 +- 在每次迭代中,它提取`tmp`的最后 7 位并将其编码为一个字节,最高位根据是否是第一个字节来设置(如果是第一个字节,则为 0,否则为 1)。 +- 然后,整数右移 7 位,以处理下一个字节。 +- 请注意,这种编码方式实际上是从低字节到高字节的反向方式,所以接下来需要翻转这些字节。 + +1. **翻转字节**: + +- 使用`mem.reverse`翻转在固定缓冲区流中编码的字节。这是因为我们是以反序编码它们的,现在我们要将它们放在正确的顺序。 + +1. **写入结果**: + +- 使用提供的`writer`将翻转后的字节写入到目标位置。 + +## file.zig + +主要目的是为了表示和处理 MIDI 文件的不同部分,以及提供了一个迭代器来遍历 MIDI 轨道的事件。 + +```zig +const midi = @import("../midi.zig"); +const std = @import("std"); +const decode = @import("./decode.zig"); + +const io = std.io; +const mem = std.mem; + +pub const Header = struct { + chunk: Chunk, + format: u16, + tracks: u16, + division: u16, + + pub const size = 6; +}; + +pub const Chunk = struct { + kind: [4]u8, + len: u32, + + pub const file_header = "MThd"; + pub const track_header = "MTrk"; +}; + +pub const MetaEvent = struct { + kind_byte: u8, + len: u28, + + pub fn kind(event: MetaEvent) Kind { + return switch (event.kind_byte) { + 0x00 => .SequenceNumber, + 0x01 => .TextEvent, + 0x02 => .CopyrightNotice, + 0x03 => .TrackName, + 0x04 => .InstrumentName, + 0x05 => .Luric, + 0x06 => .Marker, + 0x20 => .MidiChannelPrefix, + 0x2F => .EndOfTrack, + 0x51 => .SetTempo, + 0x54 => .SmpteOffset, + 0x58 => .TimeSignature, + 0x59 => .KeySignature, + 0x7F => .SequencerSpecificMetaEvent, + else => .Undefined, + }; + } + + pub const Kind = enum { + Undefined, + SequenceNumber, + TextEvent, + CopyrightNotice, + TrackName, + InstrumentName, + Luric, + Marker, + CuePoint, + MidiChannelPrefix, + EndOfTrack, + SetTempo, + SmpteOffset, + TimeSignature, + KeySignature, + SequencerSpecificMetaEvent, + }; +}; + +pub const TrackEvent = struct { + delta_time: u28, + kind: Kind, + + pub const Kind = union(enum) { + MidiEvent: midi.Message, + MetaEvent: MetaEvent, + }; +}; + +pub const File = struct { + format: u16, + division: u16, + header_data: []const u8 = &[_]u8{}, + chunks: []const FileChunk = &[_]FileChunk{}, + + pub const FileChunk = struct { + kind: [4]u8, + bytes: []const u8, + }; + + pub fn deinit(file: File, allocator: *mem.Allocator) void { + for (file.chunks) |chunk| + allocator.free(chunk.bytes); + allocator.free(file.chunks); + allocator.free(file.header_data); + } +}; + +pub const TrackIterator = struct { + stream: io.FixedBufferStream([]const u8), + last_event: ?TrackEvent = null, + + pub fn init(bytes: []const u8) TrackIterator { + return .{ .stream = io.fixedBufferStream(bytes) }; + } + + pub const Result = struct { + event: TrackEvent, + data: []const u8, + }; + + pub fn next(it: *TrackIterator) ?Result { + const s = it.stream.inStream(); + var event = decode.trackEvent(s, it.last_event) catch return null; + it.last_event = event; + + const start = it.stream.pos; + + var end: usize = switch (event.kind) { + .MetaEvent => |meta_event| blk: { + it.stream.pos += meta_event.len; + break :blk it.stream.pos; + }, + .MidiEvent => |midi_event| blk: { + if (midi_event.kind() == .ExclusiveStart) { + while ((try s.readByte()) != 0xF7) {} + break :blk it.stream.pos - 1; + } + break :blk it.stream.pos; + }, + }; + + return Result{ + .event = event, + .data = s.buffer[start..end], + }; + } +}; +``` + +1. **Header 结构**: + +- 表示 MIDI 文件的头部。 +- 包含一个块、格式、轨道数以及除法。 + +2. **Chunk 结构**: + +- 表示 MIDI 文件中的块,每个块有一个种类和长度。 +- 定义了文件头和轨道头的常量。 + +3. **MetaEvent 结构**: + +- 表示 MIDI 的元事件。 +- 它有一个种类字节和长度。 +- 有一个函数,根据种类字节返回事件的种类。 +- 定义了所有可能的元事件种类。 + +4. **TrackEvent 结构**: + +- 表示 MIDI 轨道中的事件。 +- 它有一个 delta 时间和种类。 +- 事件种类可以是 MIDI 事件或元事件。 + +5. **File 结构**: + +- 表示整个 MIDI 文件。 +- 它有格式、除法、头部数据和一系列块。 +- 定义了一个子结构 FileChunk,用于表示文件块的种类和字节数据。 +- 提供了一个清除方法来释放文件的资源。 + +6. **TrackIterator 结构**: + +- 是一个迭代器,用于遍历 MIDI 轨道的事件。 +- 它使用一个 FixedBufferStream 来读取事件。 +- 定义了一个 Result 结构来返回事件和关联的数据。 +- 提供了一个`next`方法来读取下一个事件。 + +## Build.zig + +buid.zig 是一个 Zig 构建脚本(build.zig),用于配置和驱动 Zig 的构建过程。 + +```zig +const builtin = @import("builtin"); +const std = @import("std"); + +const Builder = std.build.Builder; +const Mode = builtin.Mode; + +pub fn build(b: *Builder) void { + const test_all_step = b.step("test", "Run all tests in all modes."); + inline for (@typeInfo(std.builtin.Mode).Enum.fields) |field| { + const test_mode = @field(std.builtin.Mode, field.name); + const mode_str = @tagName(test_mode); + + const tests = b.addTest("midi.zig"); + tests.setBuildMode(test_mode); + tests.setNamePrefix(mode_str ++ " "); + + const test_step = b.step("test-" ++ mode_str, "Run all tests in " ++ mode_str ++ "."); + test_step.dependOn(&tests.step); + test_all_step.dependOn(test_step); + } + + const example_step = b.step("examples", "Build examples"); + inline for ([_][]const u8{ + "midi_file_to_text_stream", + }) |example_name| { + const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig"); + example.addPackagePath("midi", "midi.zig"); + example.install(); + example_step.dependOn(&example.step); + } + + const all_step = b.step("all", "Build everything and runs all tests"); + all_step.dependOn(test_all_step); + all_step.dependOn(example_step); + + b.default_step.dependOn(all_step); +} +``` + +这个 build 比较复杂,我们逐行来解析: + +```zig +const test_all_step = b.step("test", "Run all tests in all modes."); +``` + +- 使用 b.step()方法定义了一个名为 test 的步骤。描述是“在所有模式下运行所有测试”。 + +```zig +inline for (@typeInfo(std.builtin.Mode).Enum.fields) |field| {} +``` + +- Zig 有几种构建模式,例如 Debug、ReleaseSafe 等, 上面则是为每种构建模式生成测试. + - 这里,@typeInfo()函数获取了一个类型的元信息。std.builtin.Mode 是 Zig 中定义的构建模式的枚举。Enum.fields 获取了这个枚举的所有字段。 + +```zig +const example_step = b.step("examples", "Build examples"); +``` + +- 配置示例构建,为所有示例创建的构建步骤. + +```zig +const all_step = b.step("all", "Build everything and runs all tests"); +all_step.dependOn(test_all_step); +all_step.dependOn(example_step); + +b.default_step.dependOn(all_step); +``` + +- all_step 是一个汇总步骤,它依赖于之前定义的所有其他步骤。最后,b.default_step.dependOn(all_step);确保当你仅仅执行 zig build(没有指定步骤)时,all_step 会被执行。 diff --git a/content/post/2023-12-24-zig-build-explained-part1.smd b/content/post/2023-12-24-zig-build-explained-part1.smd new file mode 100644 index 0000000..1baf9b2 --- /dev/null +++ b/content/post/2023-12-24-zig-build-explained-part1.smd @@ -0,0 +1,337 @@ +--- +.title = "zig 构建系统解析 - 第一部分", +.date = @date("2023-12-24T19:15:02+0800"), +.author = "Reco", +.layout = "post.shtml", +.draft = false, +--- + +> - 原文链接: https://zig.news/xq/zig-build-explained-part-1-59lf +> - API 适配到 Zig 0.11.0 版本 + +Zig 构建系统仍然缺少文档,对很多人来说,这是不使用它的致命理由。还有一些人经常寻找构建项目的秘诀,但也在与构建系统作斗争。 + +本系列试图深入介绍构建系统及其使用方法。 + +我们将从一个刚刚初始化的 Zig 项目开始,逐步深入到更复杂的项目。在此过程中,我们将学习如何使用库和软件包、添加 C 代码,甚至如何创建自己的构建步骤。 + +## 免责声明 + +由于我不会解释 Zig 语言的语法或语义,因此我希望你至少已经有了一些使用 Zig 的基本经验。我还将链接到标准库源代码中的几个要点,以便您了解所有这些内容的来源。我建议你阅读编译系统的源代码,因为如果你开始挖掘编译脚本中的函数,大部分内容都不言自明。所有功能都是在标准库中实现的,不存在隐藏的构建魔法。 + +## 开始 + +我们通过新建一个文件夹来创建一个新项目,并在该文件夹中调用 zig init-exe。 + +这将生成如下 build.zig 文件(我去掉了注释) + +```zig +const std = @import("std"); +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + const unit_tests = b.addTest(.{ + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + const run_unit_tests = b.addRunArtifact(unit_tests); + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_unit_tests.step); +} +``` + +## 基础知识 + +构建系统的核心理念是,Zig 工具链将编译一个 Zig 程序 (build.zig),该程序将导出一个特殊的入口点(`pub fn build(b: *std.build.Builder) void`),当我们调用 `zig build` 时,该入口点将被调用。 + +然后,该函数将创建一个由 std.build.Step 节点组成的有向无环图,其中每个步骤都将执行构建过程的一部分。 + +每个步骤都有一组依赖关系,这些依赖关系需要在步骤本身完成之前完成。作为用户,我们可以通过调用 `zig build ${step-name}` 来调用某些已命名的步骤,或者使用其中一个预定义的步骤(例如 install)。 + +要创建这样一个步骤,我们需要调用 Builder.step + +```zig +const std = @import("std"); +pub fn build(b: *std.build.Builder) void { + const named_step = b.step("step-name", "This is what is shown in help"); + _ = named_step; +} +``` + +这将为我们创建一个新的步骤 step-name,当我们调用 `zig build --help` 时将显示该步骤: + +```bash +$ zig build --help +使用方法: zig build [steps] [options] + +Steps: +install (default) Copy build artifacts to prefix path +uninstall Remove build artifacts from prefix path +step-name This is what is shown in help + +General Options: +... +``` + +请注意,除了在 zig build --help 中添加一个小条目并允许我们调用 zig build step-name 之外,这个步骤仍然没有任何作用。 + +Step 遵循与 std.mem.Allocator 相同的接口模式,需要实现一个 make 函数。步骤创建时将调用该函数。对于我们在这里创建的步骤,该函数什么也不做。 + +现在,我们需要创建一个稍正式的 Zig 程序: + +## 编译 Zig 源代码 + +要使用编译系统编译可执行文件,编译器需要使用函数 Builder.addExecutable,它将为我们创建一个新的 LibExeObjStep。这个步骤实现是 zig build-exe、zig build-lib、zig build-obj 或 zig test 的便捷封装,具体取决于初始化方式。本文稍后将对此进行详细介绍。 + +现在,让我们创建一个步骤来编译我们的 src/main.zig 文件(之前由 zig init-exe 创建) + +```zig +const std = @import("std"); +pub fn build(b: *std.build.Builder) void { + const exe = b.addExecutable(.{.name = "fresh",.root_source_file = .{ .path = "src/main.zig" },}); + const compile_step = b.step("compile", "Compiles src/main.zig"); + compile_step.dependOn(&exe.step); +} +``` + +我们在这里添加了几行。首先,const exe = b.addExecutable 将创建一个新的 LibExeObjStep,将 src/main.zig 编译成一个名为 fresh 的文件(或 Windows 上的 fresh.exe)。 + +第二个添加的内容是 compile_step.dependOn(&exe.step);。这就是我们构建依赖关系图的方法,并声明当执行 `compile_step` 时,`exe` 步骤也需要执行。 + +你可以调用 zig build,然后再调用 zig build compile 来验证这一点。第一次调用不会做任何事情,但第二次调用会输出一些编译信息。 + +这将始终在当前机器的调试模式下编译,因此对于初学者来说,这可能就足够了。但如果你想开始发布你的项目,你可能需要启用交叉编译: + +## 交叉编译 + +交叉编译是通过设置程序的目标和编译模式来实现的 + +```zig +const std = @import("std"); +pub fn build(b: *std.build.Builder) void { + const exe = b.addExecutable(.{ + .name = "fresh", + .root_source_file = .{ .path = "src/main.zig" }, + .optimize = .ReleaseSafe, + }); + const compile_step = b.step("compile", "Compiles src/main.zig"); + compile_step.dependOn(&exe.step); +} +``` + +在这里,`.optimize = .ReleaseSafe`, 将向编译调用传递 -O ReleaseSafe。但是!LibExeObjStep.setTarget 需要一个 std.zig.CrossTarget 作为参数,而你通常希望这个参数是可配置的。 + +幸运的是,构建系统为此提供了两个方便的函数: + +- Builder.standardReleaseOptions +- Builder.standardTargetOptions + +使用这些函数,可以将编译模式和目标作为命令行选项: + +```zig +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "fresh", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + const compile_step = b.step("compile", "Compiles src/main.zig"); + compile_step.dependOn(&exe.step); +} +``` + +现在,如果你调用 zig build --help 命令,就会在输出中看到以下部分,而之前这部分是空的: + +``` +Project-Specific Options: +-Dtarget=[string] The CPU architecture, OS, and ABI to build for +-Dcpu=[string] Target CPU features to add or subtract +-Doptimize=[enum] Prioritize performance, safety, or binary size (-O flag) + Supported Values: + Debug + ReleaseSafe + ReleaseFast + ReleaseSmall +``` + +前两个选项由 standardTargetOptions 添加,其他选项由 standardOptimizeOption 添加。现在,我们可以在调用构建脚本时使用这些选项: + +``` +zig build -Dtarget=x86_64-windows-gnu -Dcpu=athlon_fx +zig build -Doptimize=ReleaseSafe +zig build -Doptimize=ReleaseSmall +``` + +可以看到,对于布尔选项,我们可以省略 =true,直接设置选项本身。 + +但我们仍然必须调用 zig build 编译,因为默认调用仍然没有任何作用。让我们改变一下! + +## 安装工件 + +要安装任何东西,我们必须让它依赖于构建器的安装步骤。该步骤是已创建的,可通过 Builder.getInstallStep() 访问。我们还需要创建一个新的 InstallArtifactStep,将我们的 exe 文件复制到安装目录(通常是 zig-out) + +```zig +const std = @import("std"); +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "fresh", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + const install_exe = b.addInstallArtifact(exe, .{}); + b.getInstallStep().dependOn(&install_exe.step); +} +``` + +这将做几件事: + +1. 创建一个新的 InstallArtifactStep,将 exe 的编译结果复制到 $prefix/bin 中。 +2. 由于 InstallArtifactStep(隐含地)依赖于 exe,因此它也将编译 exe +3. 当我们调用 zig build install(或简称 zig build)时,它将创建 InstallArtifactStep。 +4. InstallArtifactStep 会将 exe 的输出文件注册到一个列表中,以便再次卸载它 + +现在,当你调用 zig build 时,你会看到一个新的目录 zig-out 被创建了.看起来有点像这样: + +``` +zig-out +└── bin + └── fresh +``` + +现在运行 ./zig-out/bin/fresh,就能看到这条信息: + +``` +info: All your codebase are belong to us. +``` + +或者,你也可以通过调用 zig build uninstall 再次卸载。这将删除 zig build install 创建的所有文件,但不会删除目录! + +由于安装过程是一个非常普通的操作,它有快捷方法,以缩短代码。 + +```zig +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "fresh", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + b.installArtifact(exe); +} +``` + +如果你在项目中内置了多个应用程序,你可能会想创建几个单独的安装步骤,并手动依赖它们,而不是直接调用 b.installArtifact(exe);,但通常这样做是正确的。 + +请注意,我们还可以使用 Builder.installFile(或其他,有很多变体)和 Builder.installDirectory 安装任何其他文件。 + +现在,从理解初始构建脚本到完全扩展,还缺少一个部分: + +## 运行已构建的应用程序 + +为了开发用户体验和一般便利性,从构建脚本中直接运行程序是非常实用的。这通常是通过运行步骤实现的,可以通过 zig build run 调用。 + +为此,我们需要一个 RunStep,它将执行我们能在系统上运行的任何可执行文件 + +```zig +const std = @import("std"); +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "fresh", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +RunStep 有几个函数可以为执行进程的 argv 添加值: + +- addArg 将向 argv 添加一个字符串参数。 +- addArgs 将同时添加多个字符串参数 +- addArtifactArg 将向 argv 添加 LibExeObjStep 的结果文件 +- addFileSourceArg 会将其他步骤生成的任何文件添加到 argv。 + +请注意,第一个参数必须是我们要运行的可执行文件的路径。在本例中,我们要运行 exe 的编译输出。 + +现在,当我们调用 zig build run 时,我们将看到与自己运行已安装的 exe 相同的输出: + +``` +info: All your codebase are belong to us. +``` + +请注意,这里有一个重要的区别: 使用 RunStep 时,我们从 ./zig-cache/.../fresh 而不是 zig-out/bin/fresh 运行可执行文件!如果你加载的文件相对于可执行路径,这一点可能很重要。 + +RunStep 的配置非常灵活,可以通过 stdin 向进程传递数据,也可以通过 stdout 和 stderr 验证输出。你还可以更改工作目录或环境变量。 + +对了,还有一件事: + +如果你想从 zig 编译命令行向进程传递参数,可以通过访问 Builder.args 来实现 + +```zig +const std = @import("std"); +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "fresh", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +这样就可以在 cli 上的 -- 后面传递参数: + +``` +zig build run -- -o foo.bin foo.asm +``` + +## 结论 + +本系列的第一章应该能让你完全理解本文开头的构建脚本,并能创建自己的构建脚本。 + +大多数项目甚至只需要编译、安装和运行一些 Zig 可执行文件,所以你就可以开始了! + +下一部分我将介绍如何构建 C 和 C++ 项目。 diff --git a/content/post/2023-12-28-zig-build-explained-part2.smd b/content/post/2023-12-28-zig-build-explained-part2.smd new file mode 100644 index 0000000..29ec328 --- /dev/null +++ b/content/post/2023-12-28-zig-build-explained-part2.smd @@ -0,0 +1,563 @@ +--- +.title = "zig 构建系统解析 - 第二部分", +.date = @date("2023-12-24T19:15:02+0800"), +.author = "Reco", +.layout = "post.shtml", +.draft = false, +--- + +> - 原文链接: https://zig.news/xq/zig-build-explained-part-2-1850 +> - API 适配到 Zig 0.11.0 版本 + +## 注释 + +从现在起,我将只提供一个最精简的 build.zig,用来说明解决一个问题所需的步骤。如果你想了解如何将所有这些文件粘合到一个构建文件中,请阅读本系列[第一篇文章](2023-12-24-zig-build-explained-part1)。 + +## 在命令行上编译 C 代码 + +Zig 有两种编译 C 代码的方法,而且这两种很容易混淆。 + +### 使用 zig cc + +Zig 提供了 LLVM c 编译器 clang。第一种是 zig cc 或 zig c++,它是与 clang 接近 1:1 的前端。由于我们无法直接从 build.zig 访问这些功能(而且我们也不需要!),所以我将在快速的介绍这个主题。 + +如前所述,zig cc 是暴露的 clang 前端。您可以直接将 CC 变量设置为 zig cc,并使用 zig cc 代替 gcc 或 clang 来使用 Makefiles、CMake 或其他编译系统,这样您就可以在已有的项目中使用 Zig 的完整交叉编译体验。请注意,这只是理论上的说法,因为很多编译系统无法处理编译器名称中的空格。解决这一问题的办法是使用一个简单的封装脚本或工具,将所有参数转发给 zig cc。 + +假设我们有一个由 main.c 和 buffer.c 生成的项目,我们可以用下面的命令行来构建它: + +``` +zig cc -o example buffer.c main.c +``` + +这将为我们创建一个名为 example 的可执行文件(在 Windows 系统中,应使用 example.exe 代替 example)。与普通的 clang 不同,Zig 默认会插入一个 -fsanitize=undefined,它将捕捉你使用的未定义行为。 + +如果不想使用,则必须通过 -fno-sanitize=undefined 或使用优化的发布模式(如 -O2)。 + +使用 zig cc 进行交叉编译与使用 Zig 本身一样简单: + +``` +zig cc -o example.exe -target x86_64-windows-gnu buffer.c main.c +``` + +如你所见,只需向 -target 传递目标三元组,就能调用交叉编译。只需确保所有外部库都已准备好进行交叉编译即可! + +## 使用 zig build-exe 和其他工具 + +使用 Zig 工具链构建 C 项目的另一种方法与构建 Zig 项目的方法相同: + +``` +zig build-exe -lc main.c buffer.c +``` + +这里的主要区别在于,必须明确传递 -lc 才能链接到 libc,而且可执行文件的名称将从传递的第一个文件中推导出。如果想使用不同的可执行文件名,可通过 --name example 再次获取示例文件。 + +交叉编译也是如此,只需通过 -target x86_64-windows-gnu 或其他目标三元组即可: + +``` +zig build-exe -lc -target x86_64-windows-gnu main.c buffer.c +``` + +你会发现,使用这条编译命令,Zig 会自动在输出文件中附加 .exe 扩展名,并生成 .pdb 调试数据库。如果你在此处传递 --name example,输出文件也会有正确的 .exe 扩展名,所以你不必考虑这个问题。 + +## 用 build.zig 创建 C 代码 + +那么,我们如何用 build.zig 来构建上面的两个示例呢? + +首先,我们需要创建一个新的编译目标: + +```zig +// demo2.1 +const std = @import("std"); +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "example", + // 这块调试了很久。最后的结论是根本不要写 + // .root_source_file = .{ .path = undefined }, + .target = target, + .optimize = optimize, + }); + // 这块调试了很久。API变了不会写,着了很久的文档和看了很久的代码 + exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("main.c"), .flags = &.{} }); + exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("buffer.c"), .flags = &.{} }); + //exe.linkLibC(); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +然后,我们通过 addCSourceFile 添加两个 C 语言文件: + +``` +exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("main.c"), .flags = &.{} }); +exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("buffer.c"), .flags = &.{} }); +``` + +第一个参数 addCSourceFile 是要添加的 C 或 C++ 文件的名称,第二个参数是该文件要使用的命令行选项列表。 + +请注意,我们向 addExecutable 传递的是空值,因为我们没有要编译的 Zig 源文件。 + +现在,调用 zig build 可以正常运行,并在 zig-out/bin 中生成一个可执行文件。很好,我们用 Zig 构建了第一个小 C 项目! + +如果你想跳过检查 C 代码中的未定义行为,就必须在调用时添加选项: + +```zig +exe.addCSourceFile(.{.file = std.build.LazyPath.relative("buffer.c"), .flags = &.{"-fno-sanitize=undefined"}}); +``` + +## 使用外部库 + +通常情况下,C 项目依赖于其他库,这些库通常预装在 Unix 系统中,或通过软件包管理器提供。 + +为了演示这一点,我们创建一个小工具,通过 curl 库下载文件,并将文件内容打印到标准输出: + +```c +#include +#include + +static size_t writeData(void *ptr, size_t size, size_t nmemb, FILE *stream) { + size_t written; + written = fwrite(ptr, size, nmemb, stream); + return written; +} + +int main(int argc, char ** argv) +{ + if(argc != 2) + return 1; + + char const * url = argv[1]; + CURL * curl = curl_easy_init(); + if (curl == NULL) + return 1; + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, stdout); + CURLcode res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + if(res != CURLE_OK) + return 1; + + return 0; +} +``` + +要编译这个程序,我们需要向编译器提供正确的参数,包括包含路径、库和其他参数。幸运的是,我们可以使用 Zig 内置的 pkg-config 集成: + +```zig + // demo2.2 +const std = @import("std"); +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "downloader", + .target = target, + .optimize = optimize, + }); + exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("download.c"), .flags = &.{} }); + exe.linkSystemLibrary("curl"); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +让我们创建程序,并通过 URL 调用它 + +``` +zig build +./zig-out/bin/downloader https://mq32.de/public/ziggy.txt +``` + +## 配置路径 + +由于我们不能在交叉编译项目中使用 pkg-config,或者我们想使用预编译的专用库(如 BASS 音频库),因此我们需要配置包含路径和库路径。 + +这可以通过函数 addIncludePath 和 addLibraryPath 来完成: + +```zig +//demo 2.3 +const std = @import("std"); +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "example", + .target = target, + .optimize = optimize, + }); + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("bass-player.c"), + .flags = &.{} + }); + exe.linkLibC(); + // 还是一步步看源代码,找新的函数,addIncludeDir,addLibDir ->new function + exe.addIncludePath(std.build.LazyPath.relative("bass/linux")); + exe.addLibraryPath(std.build.LazyPath.relative("bass/linux/x64")); + exe.linkSystemLibrary("bass"); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +addIncludePath 和 addLibraryPath 都可以被多次调用,以向编译器添加多个路径。这些函数不仅会影响 C 代码,还会影响 Zig 代码,因此 @cImport 可以访问包含路径中的所有头文件。 + +## 每个文件的包含路径 + +因此,如果我们需要为每个 C 文件设置不同的包含路径,我们就需要用不同的方法来解决这个问题: +由于我们仍然可以通过 addCSourceFile 传递任何 C 编译器标志,因此我们也可以在这里手动设置包含目录。 + +```zig +//demo2.4 +const std = @import("std"); +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "example", + .target = target, + .optimize = optimize, + }); + exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("multi-main.c"), .flags = &.{} }); + exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("multi.c"), .flags = &.{ "-I", "inc1" } }); + exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("multi.c"), .flags = &.{ "-I", "inc2" } }); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +上面的示例非常简单,所以你可能会想为什么需要这样的东西。答案是,有些库的头文件名称非常通用,如 api.h 或 buffer.h,而您希望使用两个共享头文件名称的不同库。 + +## 构建 C++ 项目 + +到目前为止,我们只介绍了 C 文件,但构建 C++ 项目并不难。你仍然可以使用 addCSourceFile,但只需传递一个具有典型 C++ 文件扩展名的文件,如 cpp、cxx、c++ 或 cc: + +```zig +//demo2.5 +const std = @import("std"); +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "example", + .target = target, + .optimize = optimize, + }); + exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("main.c"), .flags = &.{} }); + exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("buffer.cc"), .flags = &.{} }); + exe.linkLibCpp(); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +如你所见,我们还需要调用 linkLibCpp,它将链接 Zig 附带的 c++ 标准库。 + +这就是构建 C++ 文件所需的全部知识,没有什么更神奇的了。 + +## 指定语言版本 + +试想一下,如果你创建了一个庞大的项目,其中的 C 或 C++ 文件有新有旧,而且可能是用不同的语言标准编写的。为此,我们可以使用编译器标志来传递 -std=c90 或 -std=c++98: + +```zig +//demo2.6 +const std = @import("std"); +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "example", + .target = target, + .optimize = optimize, + }); + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("main.c"), + .flags = &.{"-std=c90"} + }); + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("buffer.cc"), + .flags = &.{"-std=c++17"} + }); + exe.linkLibCpp(); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +## 条件编译 + +与 Zig 相比,C 和 C++ 的条件编译方式非常繁琐。由于缺乏惰性求值的功能,有时必须根据目标环境来包含/排除文件。你还必须提供宏定义来启用/禁用某些项目功能。 + +Zig 编译系统可以轻松处理这两种变体: + +```zig +//demo2.7 +const std = @import("std"); +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const use_platform_io = b.option(bool, "platform-io", "Uses the native api instead of the C wrapper") orelse true; + const exe = b.addExecutable(.{ + .name = "example", + .target = target, + .optimize = optimize, + }); + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("print-main.c"), + .flags = &.{} + }); + if (use_platform_io) { + exe.defineCMacro("USE_PLATFORM_IO", null); + if (exe.target.isWindows()) { + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("print-windows.c"), + .flags = &.{} + }); + + } else { + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("print-unix.c"), + .flags = &.{} + }); + } + } + exe.linkLibC(); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +通过 defineCMacro,我们可以定义自己的宏,就像使用 -D 编译器标志传递宏一样。第一个参数是宏名,第二个值是一个可选项,如果不为空,将设置宏的值。 + +有条件地包含文件就像使用 if 一样简单,你可以这样做。只要不根据你想在构建脚本中定义的任何约束条件调用 addCSourceFile 即可。只包含特定平台的文件?看看上面的脚本就知道了。根据系统时间包含文件?也许这不是个好主意,但还是有可能的! + +## 编译大型项目 + +由于大多数 C(更糟糕的是 C++)项目都有大量文件(SDL2 有 411 个 C 文件和 40 个 C++ 文件),我们必须找到一种更简单的方法来编译它们。调用 addCSourceFile 400 次并不能很好地扩展。 + +因此,我们可以做的第一个优化就是将 c 和 c++ 标志放入各自的变量中: + +```zig +//demo2.8 +const std = @import("std"); +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "example", + .target = target, + .optimize = optimize, + }); + const flags = .{ + "-Wall", + "-Wextra", + "-Werror=return-type", + }; + const cflags = flags ++ .{"-std=c99"}; + const cppflags = cflags ++ .{ + "-std=c++17", + "-stdlib=libc++", + "-fno-exceptions", + }; + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("main.c"), + .flags = &cflags, + }); + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("buffer.cc"), + .flags = &cppflags, + }); + exe.linkLibC(); + exe.linkLibCpp(); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +这样就可以在项目的不同组件和不同语言之间轻松共享标志。 + +addCSourceFile 还有一个变种,叫做 addCSourceFiles。它使用的不是文件名,而是可编译的所有源文件的文件名片段。这样,我们就可以收集某个文件夹中的所有文件: + +```zig +//demo2.9 +const std = @import("std"); +pub fn build(b: *std.build.Builder) !void { + var sources = std.ArrayList([]const u8).init(b.allocator); + // Search for all C/C++ files in `src` and add them + { + var dir = try std.fs.cwd().openIterableDir(".", .{ .access_sub_paths = true }); + + var walker = try dir.walk(b.allocator); + defer walker.deinit(); + + const allowed_exts = [_][]const u8{ ".c", ".cpp", ".cxx", ".c++", ".cc" }; + while (try walker.next()) |entry| { + const ext = std.fs.path.extension(entry.basename); + const include_file = for (allowed_exts) |e| { + if (std.mem.eql(u8, ext, e)) + break true; + } else false; + if (include_file) { + // we have to clone the path as walker.next() or walker.deinit() will override/kill it + try sources.append(b.dupe(entry.path)); + } + } + } + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "example", + .target = target, + .optimize = optimize, + }); + exe.addCSourceFiles(sources.items, &.{}); + exe.linkLibC(); + exe.linkLibCpp(); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +正如您所看到的,我们可以轻松搜索某个文件夹中的所有文件,匹配文件名并将它们添加到源代码集合中。然后,我们只需为每个文件集调用一次 addCSourceFiles,就可以大展身手了。 + +你可以制定很好的规则来匹配 exe.target 和文件夹名称,以便只包含通用文件和适合你的平台的文件。不过,这项工作留给读者自己去完成。 + +注意:其他构建系统会考虑文件名,而 Zig 系统不会!例如,在一个 qmake 项目中不能有两个名为 data.c 的文件!Zig 并不在乎,你可以添加任意多的同名文件,只要确保它们在不同的文件夹中就可以了 😏。 + +## 编译 Objective C + +我完全忘了!Zig 不仅支持编译 C 和 C++,还支持通过 clang 编译 Objective C! + +虽然不支持 C 或 C++,但至少在 macOS 上,你已经可以编译 Objective C 程序并添加框架了: + +```zig +//demo2.10 +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "example", + .target = target, + .optimize = optimize, + }); + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("main.m"), + .flags = &.{}, + }); + exe.linkFramework("Foundation"); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +在这里,链接 libc 是隐式的,因为添加框架会自动强制链接 libc。是不是很酷? + +## 混合使用 C 和 Zig 源代码 + +现在,是最后一章: 混合 C 代码和 Zig 代码! + +为此,我们只需将 addExecutable 中的第二个参数设置为文件名,然后点击编译! + +```zig +//demo2.11 +const std = @import("std"); +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "example", + .root_source_file = .{ .path = "main.zig" }, + .target = target, + .optimize = optimize, + }); + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("buffer.c"), + .flags = &.{}, + }); + exe.linkLibC(); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +这就是需要做的一切!是这样吗? + +实际上,有一种情况现在还没有得到很好的支持: +您应用程序的入口点现在必须在 Zig 代码中,因为根文件必须导出一个 pub fn main(...) ……。 +因此,如果你想将 C 项目中的代码移植到 Zig 中,你必须将 argc 和 argv 转发到你的 C 代码中,并将 C 代码中的 main 重命名为其他函数(例如 oldMain),然后在 Zig 中调用它。如果需要 argc 和 argv,可以通过 std.process.argsAlloc 获取。或者更好: 在 Zig 中重写你的入口点,然后从你的项目中移除一些 C 语言! + +## 结论 + +假设你只编译一个输出文件,那么现在你应该可以将几乎所有的 C/C++ 项目移植到 build.zig。 + +如果你需要一个以上的构建工件,例如共享库和可执行文件,你应该阅读下一篇文章,它将介绍如何在一个 build.zig 中组合多个项目,以创建便捷的构建体验。 + +敬请期待! diff --git a/content/post/2023-12-29-zig-build-explained-part3.smd b/content/post/2023-12-29-zig-build-explained-part3.smd new file mode 100644 index 0000000..b69f3e6 --- /dev/null +++ b/content/post/2023-12-29-zig-build-explained-part3.smd @@ -0,0 +1,504 @@ +--- +.title = "zig 构建系统解析 - 第三部分", +.date = @date("2023-12-29T19:15:02+0800"), +.author = "Reco", +.layout = "post.shtml", +.draft = false, +--- + +> - 原文链接: https://zig.news/xq/zig-build-explained-part-3-1ima +> - API 适配到 Zig 0.11.0 版本 + +从现在起,我将只提供一个最精简的 build.zig,用来说明解决一个问题所需的步骤。如果你想了解如何将所有这些文件粘合到一个构建文件中,请阅读本系列[第一篇文章](2023-12-24-zig-build-explained-part1)。 + +## 复合项目 + +有很多简单的项目只包含一个可执行文件。但是,一旦开始编写库,就必须对其进行测试,通常会编写一个或多个示例应用程序。当人们开始使用外部软件包、C 语言库、生成代码等时,复杂性也会随之上升。 + +本文试图涵盖所有这些用例,并将解释如何使用 build.zig 来编写多个程序和库。 + +## 软件包 + +译者:此处代码和说明,需要 zig build-exe --pkg-begin,但是在 0.11 已经失效。所以删除。 + +## 库 + +但 Zig 也知道库这个词。但我们不是已经讨论过外部库了吗? + +在 Zig 的世界里,库是一个预编译的静态或动态库,就像在 C/C++ 的世界里一样。库通常包含头文件(.h 或 .zig)和二进制文件(通常为 .a、.lib、.so 或 .dll)。 + +这种库的常见例子是 zlib 或 SDL。 + +与软件包相反,链接库的方式有两种 + +- (静态库)在命令行中传递文件名 +- (动态库)使用 -L 将库的文件夹添加到搜索路径中,然后使用 -l 进行实际链接。 + +在 Zig 中,我们需要导入库的头文件,如果头文件在 Zig 中,则使用包,如果是 C 语言头文件,则使用 @cImport。 + +## 工具 + +如果我们的项目越来越多,那么在构建过程中就需要使用工具。这些工具通常会完成以下任务: + +生成一些代码(如解析器生成器、序列化器或库头文件) +捆绑应用程序(例如生成 APK、捆绑应用程序……)。 +创建资产包 +... +有了 Zig,我们不仅能在构建过程中利用现有工具,还能为当前主机编译我们自己(甚至外部)的工具并运行它们。 + +但我们如何在 build.zig 中完成这些工作呢? + +## 添加软件包 + +添加软件包通常使用 LibExeObjStep 上的 addPackage 函数。该函数使用一个 std.build.Pkg 结构来描述软件包的外观: + +```zig +pub const Module = struct { + builder: *Build, + source_file: LazyPath, + dependencies: std.StringArrayHashMap(*Module), +}; +``` + +我们可以看到,它有 2 个成员: + +source_file 是定义软件包根文件的 FileSource。这通常只是指向文件的路径,如 vendor/zig-args/args.zig +dependencies 是该软件包所需的可选软件包片段。如果我们使用更复杂的软件包,这通常是必需的。 + +这是个人建议:我通常会在 build.zig 的顶部创建一个名为 pkgs 的结构/名称空间,看起来有点像这样: + +```zig +const args = b.createModule(.{ + .source_file = .{ .path = "libs/args/args.zig" }, + .dependencies = &.{}, +}); + +const interface = b.createModule(.{ + .source_file = .{ .path = "libs/interface.zig/interface.zig" }, + .dependencies = &.{}, +}); + +const lola = b.createModule(.{ + .source_file = .{ .path = "src/library/main.zig" }, + .dependencies = &.{}, +}); +const pkgs = .{ + .args = args, + + .interface = interface, + + .lola = lola, +}; +``` + +随后通过编译步骤 exe,把模块加入进来。函数 addModule 的第一个参数 name 是模块名称 + +```zig +exe.addModule("lola",pkgs.lola); +exe.addModule("args",pkgs.args); +``` + +## 添加库 + +添加库相对容易,但我们需要配置更多的路径。 + +注:在上一篇文章中,我们已经介绍了大部分内容,但现在还是让我们快速复习一遍: + +假设我们要将 libcurl 链接到我们的项目,因为我们要下载一些文件。 + +### 系统库 + +对于 unixoid 系统,我们通常可以使用系统软件包管理器来链接系统库。方法是调用 linkSystemLibrary,它会使用 pkg-config 自行找出所有路径: + +```zig +//demo 3.2 +const std = @import("std"); +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "example", + .root_source_file = .{ .path = "main.zig" }, + .target = target, + .optimize = optimize, + }); + exe.linkLibC(); + exe.linkSystemLibrary("curl"); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +对于 Linux 系统,这是链接外部库的首选方式。 + +### 本地库 + +不过,您也可以链接您作为二进制文件提供商的库。为此,我们需要调用几个函数。首先,让我们来看看这样一个库是什么样子的: + +``` +./vendor/libcurl +include +│ └── curl +│ ├── curl.h +│ ├── curlver.h +│ ├── easy.h +│ ├── mprintf.h +│ ├─── multi.h +│ ├── options.h +│ ├── stdcheaders.h +│ ├── system.h +│ ├── typecheck-gcc.h +│ └── urlapi.h +├── lib +│ ├── libcurl.a +│ ├── libcurl.so +│ └── ... +├─── bin +│ └── ... +└──share + └── ... +``` + +我们可以看到,vendor/libcurl/include 路径包含我们的头文件,vendor/libcurl/lib 文件夹包含一个静态库(libcurl.a)和一个共享/动态库(libcurl.so)。 + +### 动态链接 + +要链接 libcurl,我们需要先添加 include 路径,然后向 zig 提供库的前缀和库名:(todo 代码有待验证,因为 curl 可能需要自己编译自己生成 static lib) + +```zig +//demo 3.3 +const std = @import("std"); +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = .{ .path = "main.zig" }, + .target = target, + .optimize = optimize, + }); + b.installArtifact(exe); + exe.linkLibC(); + exe.addIncludePath(.{ .path = "vendor/libcurl/include" }); + exe.addLibraryPath(.{ .path = "vendor/libcurl/lib" }); + exe.linkSystemLibraryName("curl"); +} +``` + +addIncludePath 将文件夹添加到搜索路径中,这样 Zig 就能找到 curl/curl.h 文件。注意,我们也可以在这里传递 "vendor/libcurl/include/curl",但你通常应该检查一下你的库到底想要什么。 + +addLibraryPath 对库文件也有同样的作用。这意味着 Zig 现在也会搜索 "vendor/libcurl/lib "文件夹中的库。 + +最后,linkSystemLibrary 会告诉 Zig 搜索名为 "curl "的库。如果你留心观察,就会发现上面列表中的文件名是 libcurl.so,而不是 curl.so。在 unixoid 系统中,库文件的前缀通常是 lib,这样就不会将其传递给系统。在 Windows 系统中,库文件的名字应该是 curl.lib 或类似的名字。 + +## 静态链接 + +当我们要静态链接一个库时,我们必须采取一些不同的方法: + +```zig +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + b.installArtifact(exe); + exe.linkLibC(); + exe.addIncludeDir("vendor/libcurl/include"); + exe.addObjectFile("vendor/libcurl/lib/libcurl.a"); + exe.addIncludePath(.{ .path = "vendor/libcurl/include" }); + exe.addLibraryPath(.{ .path = "vendor/libcurl/lib" }); +} +``` + +对 addIncludeDir 的调用没有改变,但我们突然不再调用带 link 的函数了?你可能已经知道了: 静态库实际上就是对象文件的集合。在 Windows 上,这一点也很相似,据说 MSVC 也使用了相同的工具集。 + +因此,静态库就像对象文件一样,通过 addObjectFile 传递给链接器,并由其解包。 + +注意:大多数静态库都有一些传递依赖关系。在我编译 libcurl 的例子中,就有 nghttp2、zstd、z 和 pthread,我们需要再次手动链接它们: + +```zig +// 示例片段 +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + b.installArtifact(exe); + exe.linkLibC(); + exe.addIncludePath(.{ .path = "vendor/libcurl/include" }); + exe.addLibraryPath(.{ .path = "vendor/libcurl/lib" }); + exe.linkSystemLibrary("nghttp2"); + exe.linkSystemLibrary("zstd"); + exe.linkSystemLibrary("z"); + exe.linkSystemLibrary("pthread"); +} +``` + +我们可以继续静态链接越来越多的库,并拉入完整的依赖关系树。 + +## 通过源代码链接库 + +不过,我们还有一种与 Zig 工具链截然不同的链接库方式: + +我们可以自己编译它们! + +这样做的好处是,我们可以更容易地交叉编译我们的程序。为此,我们需要将库的构建文件转换成我们的 build.zig。这通常需要对 build.zig 和你的库所使用的构建系统都有很好的了解。但让我们假设这个库是超级简单的,只是由一堆 C 文件组成: + +```zig +// 示例片段 +pub fn build(b: *std.build.Builder) void { + const cflags = .{}; + + const curl = b.addSharedLibrary("curl", null, .unversioned); + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("vendor/libcurl/src/tool_main.c"), + .flags = &cflags, + }); + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("vendor/libcurl/src/tool_msgs.c"), + .flags = &cflags, + }); + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("vendor/libcurl/src/tool_dirhie.c"), + .flags = &cflags, + }); + exe.addCSourceFile(.{ + .file = std.build.LazyPath.relative("vendor/libcurl/src/tool_doswin.c"), + .flags = &cflags, + }); + const target = b.standardTargetOptions(.{}); + exe.linkLibC(); + exe.addIncludePath(.{ .path = "vendor/libcurl/include" }); + exe.linkLibrary(curl); + b.installArtifact(exe); + +} +``` + +这样,我们就可以使用 addSharedLibrary 和 addStaticLibrary 向 LibExeObjStep 添加库。 + +这一点尤其方便,因为我们可以使用 setTarget 和 setBuildMode 从任何地方编译到任何地方。 + +## 使用工具 + +在工作流程中使用工具,通常是在需要以 bison、flex、protobuf 或其他形式进行预编译时。工具的其他用例包括将输出文件转换为不同格式(如固件映像)或捆绑最终应用程序。 + +系统工具 +使用预装的系统工具非常简单,只需使用 addSystemCommand 创建一个新步骤即可: + +```zig +// demo 3.5 +const std = @import("std"); +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + const cmd = b.addSystemCommand(&.{ + "flex", + "-outfile=lines.c", + "lines.l", + }); + b.installArtifact(exe); + exe.step.dependOn(&cmd.step); +} +``` + +从这里可以看出,我们只是向 addSystemCommand 传递了一个选项数组,该数组将反映我们的命令行调用。然后,我们按照习惯创建可执行文件,并使用 dependOn 在 cmd 上添加步骤依赖关系。 + +我们也可以反其道而行之,在编译程序时添加有关程序的小信息: + +```zig +//demo3.6 +const std = @import("std"); +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = .{ .path = "main.zig" }, + .target = target, + .optimize = optimize, + }); + b.installArtifact(exe); + const cmd = b.addSystemCommand(&.{"size"}); + cmd.addArtifactArg(exe); + b.getInstallStep().dependOn(&cmd.step); +} +``` + +size 是一个很好的工具,它可以输出有关可执行文件代码大小的信息,可能如下所示: + +文本 数据 BSS Dec 十六进制 文件名 +12377 620 104 13101 332d ... + +如您所见,我们在这里使用了 addArtifactArg,因为 addSystemCommand 只会返回一个 std.build.RunStep。这样,我们就可以增量构建完整的命令行,包括任何 LibExeObjStep 输出、FileSource 或逐字参数。 + +## 全新工具 + +最酷的是 我们还可以从 LibExeObjStep 获取 std.build.RunStep: + +```zig +// 示例片段 +const std = @import("std"); +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const game = b.addExecutable(.{ + .name = "game", + .root_source_file = .{ .path = "src/game.zig" }, + .target = target, + .optimize = optimize, + }); + b.installArtifact(game); + const pack_tool = b.addExecutable(.{ + .name = "pack", + .root_source_file = .{ .path = "tools/pack.zig" }, + .target = target, + .optimize = optimize, + }); + //译者改动:const precompilation = pack_tool.run(); // returns *RunStep + const precompilation = b.addRunArtifact(pack_tool); + precompilation.addArtifactArg(game); + precompilation.addArg("assets.zip"); + + const pack_step = b.step("pack", "Packs the game and assets together"); + pack_step.dependOn(&precompilation.step); +} +``` + +此构建脚本将首先编译一个名为 pack 的可执行文件。然后将以我们的游戏和 assets.zig 文件作为命令行参数调用该可执行文件。 + +调用 zig build pack 时,我们将运行 tools/pack.zig。这很酷,因为我们还可以从头开始编译所需的工具。为了获得最佳的开发体验,你甚至可以从源代码编译像 bison 这样的 "外部 "工具,这样就不会依赖系统了! + +## 将所有内容放在一起 + +一开始,所有这些都会让人望而生畏,但如果我们看一个更大的 build.zig 实例,就会发现一个好的构建文件结构会给我们带来很大帮助。 + +下面的编译脚本将编译一个虚构的工具,它可以通过 flex 生成的词法器解析输入文件,然后使用 curl 连接到服务器,并在那里传送一些文件。当我们调用 zig build deploy 时,项目将被打包成一个 zip 文件。正常的 zig 编译调用只会准备一个未打包的本地调试安装。 + +```zig +// 示例片段 +const std = @import("std"); +pub fn build(b: *std.build.Builder) void { + const mode = b.standardOptimizeOption(.{}); + // const mode = b.standardReleaseOptions(); + + const target = b.standardTargetOptions(.{}); + + // Generates the lex-based parser + const parser_gen = b.addSystemCommand(&[_][]const u8{ + "flex", + "--outfile=review-parser.c", + "review-parser.l", + }); + + // Our application + const exe = b.addExecutable(.{ + .name = "upload-review", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = mode, + }); + + { + exe.step.dependOn(&parser_gen.step); + + exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("review-parser.c"), .flags = &.{} }); + + // add zig-args to parse arguments + + const ap = b.createModule(.{ + .source_file = .{ .path = "vendor/zig-args/args.zig" }, + .dependencies = &.{}, + }); + exe.addModule("args-parser", ap); + + // add libcurl for uploading + exe.addIncludePath(std.build.LazyPath.relative("vendor/libcurl/include")); + exe.addObjectFile(std.build.LazyPath.relative("vendor/libcurl/lib/libcurl.a")); + + exe.linkLibC(); + b.installArtifact(exe); + // exe.install(); + } + + // Our test suite + const test_step = b.step("test", "Runs the test suite"); + const test_suite = b.addTest(.{ + .root_source_file = .{ .path = "src/tests.zig" }, + }); + + test_suite.step.dependOn(&parser_gen.step); + exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("review-parser.c"), .flags = &.{} }); + + // add libcurl for uploading + exe.addIncludePath(std.build.LazyPath.relative("vendor/libcurl/include")); + exe.addObjectFile(std.build.LazyPath.relative("vendor/libcurl/lib/libcurl.a")); + + test_suite.linkLibC(); + + test_step.dependOn(&test_suite.step); + + { + const deploy_step = b.step("deploy", "Creates an application bundle"); + + // compile the app bundler + const deploy_tool = b.addExecutable(.{ + .name = "deploy", + .root_source_file = .{ .path = "tools/deploy.zig" }, + .target = target, + .optimize = mode, + }); + + { + deploy_tool.linkLibC(); + deploy_tool.linkSystemLibrary("libzip"); + } + + const bundle_app = b.addRunArtifact(deploy_tool); + bundle_app.addArg("app-bundle.zip"); + bundle_app.addArtifactArg(exe); + bundle_app.addArg("resources/index.htm"); + bundle_app.addArg("resources/style.css"); + + deploy_step.dependOn(&bundle_app.step); + } +} +``` + +如你所见,代码量很大,但通过使用块,我们可以将构建脚本结构化为逻辑组。 + +如果你想知道为什么我们不为 deploy_tool 和 test_suite 设置目标: +两者都是为了在主机平台上运行,而不是在目标机器上。 +此外,deploy_tool 还设置了固定的编译模式,因为我们希望快速编译,即使我们编译的是应用程序的调试版本。 + +## 总结 + +看完这一大堆文字,你现在应该可以构建任何你想要的项目了。我们已经学会了如何编译 Zig 应用程序,如何为其添加任何类型的外部库,甚至如何为发布管理对应用程序进行后处理。 + +我们还可以通过少量的工作来构建 C 和 C++ 项目,并将它们部署到各个地方,而不仅仅是 Zig 项目。 + +即使我们混合使用项目、工具和其他一切。一个 build.zig 文件就能满足我们的需求。但很快你就会发现... 编译文件很快就会重复,而且有些软件包或库需要大量代码才能正确设置。 + +在下一篇文章中,我们将学习如何将 build.zig 文件模块化,如何为 Zig 创建方便的 sdks,甚至如何创建自己的构建步骤! + +一如既往,继续黑客之旅! diff --git a/content/post/2024-01-12-how-to-release-your-zig-applications.smd b/content/post/2024-01-12-how-to-release-your-zig-applications.smd new file mode 100644 index 0000000..57466d7 --- /dev/null +++ b/content/post/2024-01-12-how-to-release-your-zig-applications.smd @@ -0,0 +1,182 @@ +--- +.title = "如何发布 Zig 应用程序", +.date = @date("2024-01-12T12:04:50-0500"), +.author = "Rui Chen", +.layout = "post.shtml", +.draft = false, +--- + +> - 原文链接: https://zig.news/kristoff/how-to-release-your-zig-applications-2h90 +> - API 适配到 Zig 0.12.0 版本 +> - 本文配套代码在[这里](https://github.com/zigcc/zigcc.github.io/tree/main/examples/20240112)找到 + +你刚用 Zig 写了一个应用程序,并希望其他人使用它。 +让用户方便使用的一种方式是为他们提供应用程序的预构建可执行文件。 +接下来,我将讨论一个好的发版流程所需要正确处理的两个主要事项。 + +# 为什么提供预构建的可执行文件? + +鉴于 C/C++ 依赖系统如何工作(或者说 _不工作_),对于某些 C/C++ 项目来说, +提供预编译好的的可执行文件几乎是必须的, +否则,普通人将陷入构建系统和配置系统的泥潭, +而这些系统的数量还要乘以项目的依赖数量。 +使用 Zig 的话就不应该这样,因为 Zig 构建系统(加上即将推出的 Zig 包管理器)将能够处理一切,这意味着大多数编写良好的应用程序应该只需运行 `zig build` 即可成功构建。 + +话虽如此,你的应用程序越受欢迎,用户就越不关心它是用哪种语言编写的。 +你的用户不想安装 Zig 并运行构建过程就能轻松使用应用程序(99%的情况下,稍后会讲到剩下的 1%), +因此最好还是预先构建你的应用程序。 + +# `zig build` vs `zig build-exe` + +在本文中,我们将看到如何为 Zig 项目制作、发布构建, +因此值得花一点时间来完全理解 Zig 构建系统和命令行之间的关系。 + +如果你有一个非常简单的 Zig 应用程序(例如,单个文件,无任何依赖), +构建项目最简单的方式是使用 `zig build-exe myapp.zig`, +这会在当前路径下创建一个可执行文件。 + +随着项目的增长,特别是开始有依赖之后,你可能想添加一个 `build.zig` 文件, +并开始用到 Zig 构建系统。一旦你开始这么做,你就可以完全控制命令行参数来影响构建过程。 + +你可以使用 `zig init-exe` 来了解基线 `build.zig` 文件的样子。 +请注意,文件中的每一行代码都是显示定义,从而影响 `zig build` 子命令的行为。 + +最后一点需要注意的是,尽管使用 `zig build` 和 `zig build-exe` 时命令行参数会有所不同, +但在构建 Zig 代码方面,两者是等价的。更具体地说,尽管 Zig 构建可以调用任意命令, +并做其他可能根本与 Zig 代码无关的事情,但在构建 Zig 代码方面, +`zig build` 所做的一切就是为 `build-exe` 准备命令行参数。 +这意味着,在编译 Zig 代码方面,`zig build`(假定 `build.zig` 中的代码是正确的) +和 `zig build-exe` 之间是一一对应关系。唯一的区别只是便利性。 + +# 构建模式 + +使用 `zig build` 或 `zig build-exe myapp.zig` 构建一个 Zig 项目时, +默认得到是一个调试构建的可执行文件。调试构建主要是为了开发方便,因而,通常被认为不适合发版。 +调试构建旨在牺牲运行性能(运行更慢)来提高构建速度(编译更快), +不久, Zig 编译器将通过引入增量编译和就地二进制补丁(in-place binary patching) +来让这种权衡变得更加明显。 + +Zig 目前有三种主要的发版构建模式:`ReleaseSafe`、`ReleaseFast` 和 `ReleaseSmall`。 + +`ReleaseSafe` 应被视为发版时使用的主要模式:尽管使用了优化, +但仍保留了某些安全检查(例如,溢出和数组越界), +在发布处理不可控输入源(如互联网)的软件时,这些开销是绝对值得的。 + +`ReleaseFast` 旨在用于性能是主要关注点的软件, +例如视频游戏。这种构建模式不仅禁用了上述安全检查, +而且为了进行更激进的优化,它还假设代码中不存在这类编程错误。 + +`ReleaseSmall` 类似于 `ReleaseFast`(即,没有安全检查), +但它不是优先考虑性能,而是尝试最小化可执行文件大小。 +例如,这是一种对于 WebAssembly 来说非常有意义的构建模式, +因为你希望可执行文件尽可能小,而沙箱运行环境已经提供了很多安全保障。 + +# 如何设置构建模式 + +使用 `zig build-exe` 时,你可以添加 `-O ReleaseSafe` +(或 `ReleaseFast`,或 `ReleaseSmall`)以获得相应的构建模式。 + +使用 `zig build` 时,取决于构建脚本的配置。默认构建脚本将包含以下代码行: + +```zig +// standardReleaseOptions 允许我们在运行 zig build 时,手动选择需要构建的目标平台和架构 +// 默认情况下为本机构建 +const target = b.standardTargetOptions(.{}); + +// standardOptimizeOption 允许我们在运行 zig build 时,手动选择构建模式 +// 默认情况下为 Debug +const optimize = b.standardOptimizeOption(.{}); + +// 标准构建一个可执行二进制程序的步骤 +const exe = b.addExecutable(.{ + .name = "zig", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, +}); +``` + +这是你在命令行中指定发布模式的方式:`zig build -Doptimize=ReleaseSafe`(或 +`-Doptimize=ReleaseFast`,或 `-Doptimize=ReleaseSmall`)。 + +# 选择正确的构建目标 + +现在,我们已经选择了正确的发版模式,是时候考虑构建目标了。 +显而易见,如果使用的平台和构建平台不相同时,需要指定相应的构建目标, +但即使只打算为同一平台发版,也还是需要注意。 + +为了方便起见,假定你用的是 Windows 10,并试图为使用 Windows 10 的朋友构建可执行文件。 +最想当然的方式是直接调用 `zig build` 或 `zig build-exe`(参见前文关于两个命令之间的差异与相似之处),然后将生成的可执行文件发送给你的朋友。 + +如果这样做,有时它会工作,但有时它会因`非法指令`(或类似错误)而崩溃。这到底发生了什么? + +# CPU 特性 + +在构建时如果不指定构建目标,Zig 将面向当前的机器进行构建优化, +这意味着将利用当前 CPU 支持的所有指令集。如果 CPU 支持 AVX 扩展, +那么 Zig 将使用它来执行 SIMD 操作。但不幸的是, +这也意味着,如果你朋友的 CPU 没有 AVX 扩展,那么应用程序将崩溃, +因为这个可执行文件确实包含非法指令。 + +解决这个问题最简单的方法是:始终在进行发布时指定一个构建目标。 +没错,如果你指定你想要为 `x86-64-linux` 构建, +Zig 将设定一个与该系列所有 CPU 完全兼容的基线 CPU。 + +如果你想微调指令集的选择,你可以查看 `zig build` 的 `-Dcpu` 和 `zig build-exe` 的 +`-mcpu`。我不会在这篇文章中更多地涉及这些细节。 + +实操中,下面的命令行将是你为 Arm macOS 发版时会用到的构建命令: + +```zig +$ zig build -Dtarget=aarch64-macos +$ zig build-exe myapp.zig -target aarch64-macos +``` + +请注意,目前在使用 `zig build` 时 `=` 是必需的, +而在使用 `build-exe` 时它不起作用(即你必须在 `-target` 及其值之间放一个空格)。 +希望这些怪异的地方在不久将来会得到清理。 + +其它一些相关的构建目标: + +```zig +x86-64-linux // uses gnu libc +x86-64-linux-gnu // uses glibc +x86-64-musl // uses musl libc +x86-64-windows // uses MingW headers +x86-64-windows-msvc // uses MSVC headers but they need to be present in your system +wasm32-freestanding // you will have to use build-obj since wasm modules are not full exes +``` + +你可以通过调用 `zig targets` 看到 Zig 支持的目标 CPU 和 +操作系统(以及 libc 和指令集)的完整列表。温馨提示:这是一个很长的列表。 + +最后,别忘了 `build.zig` 里的一切都必须明确定义,因此目标选项可以通过以下几行代码设置: + +```zig +// standardReleaseOptions 允许我们在运行 zig build 时,手动选择需要构建的目标平台和架构 +// 默认情况下为本机构建 +const target = b.standardTargetOptions(.{}); + +// standardOptimizeOption 允许我们在运行 zig build 时,手动选择构建模式 +// 默认情况下为 Debug +const optimize = b.standardOptimizeOption(.{}); + +// 标准构建一个可执行二进制程序的步骤 +const exe = b.addExecutable(.{ + .name = "zig", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, +}); +``` + +这也意味着如果你想添加其他限制或以某种方式改变构建时应该如何指定目标, +你可以通过添加自己的代码来实现。 + +# 结束语 + +现在你已经了解了在进行发布构建时需要确保正确的事项:选择一个发布优化模式并选择正确的构建目标, +包括为你正在构建的同一系统进行发版构建。 + +这最后一点的一个有趣含义是,对于你的一些用户(通常情况下为 1%,乐观估计), +从头开始构建程序实际上更为可取,以确保他们充分利用其 CPU 的能力。 diff --git a/content/post/2024-04-06-zig-cpp.smd b/content/post/2024-04-06-zig-cpp.smd new file mode 100644 index 0000000..8b12c40 --- /dev/null +++ b/content/post/2024-04-06-zig-cpp.smd @@ -0,0 +1,168 @@ +--- +.title = "通过 Zig,学习 C++ 元编程", +.date = @date("2024-04-06T19:57:49+08:00"), +.author = "Peng He", +.layout = "post.shtml", +.draft = false, +--- + +尽管 Zig 社区宣称 Zig 语言是一个更好的 C (better C),但是我个人在学习 Zig 语言时经常会“触类旁通”C++。在这里列举一些例子来说明我的一些体会,可能会有一些不正确的地方,欢迎批评指正。 + +# “元能力” vs “元类型” + +在我看来,C++的增强方式是希望赋予语言一种“元能力”,能够让人重新发明新的类型,使得使用 C++的程序员使用自定义的类型,进行一种类似于“领域内语言”(DSL)编程。一个通常的说法就是 C++中任何类型定义都像是在模仿基本类型`int`。比如我们有一种类型 T,那么我们则需要定义 T 在以下几种使用场景的行为: + +```cpp +T x; //构造 +T x = y; //隐式拷贝构造 +T x{y}; //显示拷贝构造 +x = y; //x的类型是T,复制运算符重载,当然也有可能是移动运算符重载。 +//以及一大堆其他行为,比如析构等等。 +``` + +通过定义各种行为,程序员可以用 C++去模拟基础类型`int`,自定义的创造新类型。但是 Zig 却采取了另一条路,这里我觉得 Zig 的取舍挺有意思,即它剥夺了程序员定义新类型的能力,只遵循 C 的思路,即`struct`就是`struct`,他和`int`就是不一样的,没有必要通过各种运算符重载来制造一种“幻觉”,模拟`int`。相反,Zig 吸收现代语言中最有用的“元类型”,比如`slice`,`tuple`,`tagged union`等作为语言内置的基本类型,从这一点上对 C 进行增强。虽然这样降低了语言的表现力,但是却简化了设计,降低了“心智负担”。 + +比如 Zig 里的`tuple`,C++里也有`std::tuple`。当然,`std::tuple`是通过一系列的模板元编程的方式实现的,但是这个在 Zig 里是内置的,因此写代码时出现语法错误,Zig 可以直接告诉你是`tuple`用的不对,但是 C++则会打印很多错误日志。再比如`optional`,C++里也有`std::optinonal`,Zig 里只用`?T`。C++里有`std::variant`,而 Zig 里有`tagged union`。当然我们可以说,C++因为具备了这种元能力,当语法不足够“甜”时,我们可以发明新的轮子,但是代价就是系统愈发的复杂。而 Zig 则持续保持简单。 + +不过话说回来,很多底层系统的开发需求往往和这种类型系统的构建相悖,比如如果你的类型就是一个`int`的封装,那么即使发生拷贝你也无所谓性能开销。但是如果是一个`struct`,那么通常情况下,你会比较 care 拷贝,而可能考虑“移动”之类的手段。这个时候各种 C++的提供的幻觉,就成了程序员开发的绊脚石,经常你需要分析一段 C++表达式里到底有没有发生拷贝,他是左值还是右值,其实你在写 C 语言的时候也很少去考虑了这些,你在 Zig 里同样也不需要。 + +# 类型系统 + +C 语言最大弊病就是没有提供标准库,C++的标准库你要是能看懂,得具备相当的 C++的语法知识,但是 Zig 的标准库几乎不需要文档就能看懂。这其实是因为,在 C++里,类型不是一等成员(first class member),因此实现一些模版元编程算法特别不直观。但是在 Zig 里,`type`就是一等成员,比如你可以写: + +```zig +const x: type = u32; +``` + +即,把一个`type`当成一个变量使用。但是 C++里如何来实现这一行代码呢?其实是如下: + +```cpp +using x = uint32_t; +``` + +那么我们如果要对某个类型做个计算,比如组合一个新类型,Zig 里其实非常直观 + +```zig +fn Some(comptime InputType: type) type +``` + +即输入一个类型,输出一个新类型,那么 C++里对应的东西是啥呢? + +```cpp +template +struct Some { + using OutputType = ... +} +``` + +相比之下, Zig 直观太多。那么很自然的,计算一个类型,Zig 里就是调用函数,而 C++则是模板类实例化,然后访问类成员。 + +```cpp +Some::OutputType +``` + +相当于对于 InputType 调用一个 Some“函数”,然后输出一个 OutputType。 + +# 命令式 VS 声明式 + +比如实现一个函数,输入一个 bool 值,根据 bool 值,如果为真,那么输出 type A,如果为假那么输出 type B。 + +```cpp +//基本模式 +template +struct Fn { + using OutputType = A; +}; + +//特例化的模式 +template +struct Fn { + using OutputType = B; +}; +``` + +从这里 C++代码可以感觉出,其实你是拿着尺子,对照着基础模式,然后通过模版偏特化来实现不同分支的逻辑。 + +```cpp +Fn sizeof(B), A, B>::OutputType +``` + +这段代码表面上看是声明了一个类型 OutputType,而这个类型的生成依赖于一些条件。而这些条件就是模板元编程,用来从 A 和 B 中选择类型大小更大的类型,如果想要表达更复杂的逻辑,则需要掌握更多模板的奇技淫巧。 + +如果用 Zig 来做,则要简单的多: + +```zig +fn Fn(comptime A:type, comptime B: type) type { + if (@sizeOf(A) > @sizeOf(B)) { + return A; + } + return B; +} +``` + +这段代码和普通的 [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) 逻辑没什么区别,特殊的地方在于操作的对象是『类型』。 + +我们再来看递归的列子。比如有一个类型的 list,我们需要返回其中第 N 个 type。同样,由于在 C++中,类型不是一等成员,因此我们不可能有一个`vector`的东东。那怎么办呢?方法就是直接把`type list`放在模板的参数列表里:`typename ...T`。 + +于是,我们写出“函数原型”: + +```cpp +template +struct Fn; +``` + +然后我们递归的基础情况 + +```cpp +template +struct Fn<0, Head, Tail...> { + using Output = Head; +}; +``` + +然后写递归式, + +```cpp +template +struct Fn : public Fn +{ +}; +``` + +这个地方其实稍微有点难理解,其实就是拿着`...T`来模式匹配`Head, ...Tail`。 + +第一个偏特化,如果用命令式,类似于, + +```cpp +if (Index == 0) + return Head; +``` + +第二个偏特化,类似于 + +```cpp +else { + return Fn(Index-1, Tail...); +} +``` + +这里利用的其实是继承,让模板推导一路继承下去,如果 Index 不等于 0,那么`Fn`类其实是空类,即,我们无法继承到`using Output = ...`的这个`Output`。但是 index 总会等于 0,那么到了等于 0 的那天,递归就终止了,因为,我们不需要继续 Index - 1 下去了,编译器会选择特化好的`Fn<0, T,Tail...>`这个特化,而不会选择继续递归。 + +但是 Zig 实现这个也很直观,由于`slice`和`type`都是内置的,我们可以直接: + +```zig +fn chooseN(N: u32, comptime type_list: []const type) type { + return type_list[N]; +} + +pub fn main() void { + const type_list = &[_]type{ u8, u16, u32, u64 }; + std.debug.print("{}\n", .{chooseN(2, type_list)}); +} +``` + +即这个也是完全命令式的。当然 C++20 之后也出现了`if constexpr`和`concept`来进一步简化模版元编程,C++的元编程也在向命令式的方向进化。 + +# 结束语 + +尽管 Zig 目前“还不成熟”,但是学习 Zig,如果采用一种对照的思路,偶尔也会“触类旁通”C++,达到举一反三的效果。 diff --git a/content/post/2024-05-07-package-hash.smd b/content/post/2024-05-07-package-hash.smd new file mode 100644 index 0000000..d0b4a92 --- /dev/null +++ b/content/post/2024-05-07-package-hash.smd @@ -0,0 +1,239 @@ +--- +.title = "build.zig.zon 中的依赖项哈希值", +.date = @date("2024-05-07T02:45:10.692Z"), +.author = "Wenxuan Feng", +.layout = "post.shtml", +.draft = false, +--- + +> 原文地址:[build.zig.zon dependency hashes](https://zig.news/michalsieron/buildzigzon-dependency-hashes-47kj) + +# 引言 + +作者 Michał Sieroń 最近在思考 `build.zig.zon` 中的依赖项哈希值的问题。这些哈希值都有相同的前缀,而这对加密哈希函数来说极其不同寻常。习惯性使用 Conda 和 Yocto 对下载的压缩包运行 sha256sum,但生成的摘要与 `build.zig.zon` 中的哈希值完全不同。 + +```bash +.dependencies = .{ + .mach_freetype = .{ + .url = "https://pkg.machengine.org/mach-freetype/309be0cf11a2f617d06ee5c5bb1a88d5197d8b46.tar.gz", + .hash = "1220fcebb0b1a4561f9d116182e30d0a91d2a556dad8564c8f425fd729556dedc7cf", + .lazy = true, + }, + .font_assets = .{ + .url = "https://github.com/hexops/font-assets/archive/7977df057e855140a207de49039114f1ab8e6c2d.tar.gz", + .hash = "12208106eef051bc730bac17c2d10f7e42ea63b579b919480fec86b7c75a620c75d4", + .lazy = true, + }, + .mach_gpu_dawn = .{ + .url = "https://pkg.machengine.org/mach-gpu-dawn/cce4d19945ca6102162b0cbbc546648edb38dc41.tar.gz", + .hash = "1220a6e3f4772fed665bb5b1792cf5cff8ac51af42a57ad8d276e394ae19f310a92e", +} +``` + +以上摘录取自 [hexops/mach](https://github.com/hexops/mach/blob/bffc66800584123e2844c4bc4b577940806f9088/build.zig.zon#L13-L26) 项目。 + +# 初步探索 + +经过一番探索,我找到了一个文档:[doc/build.zig.zon.md](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/doc/build.zig.zon.md),似乎没有任何线索指向它。而文档中对哈希有段简短的描述。 + +- **哈希** +- 类型为字符串。 +- **[多重哈希](https://multiformats.io/multihash/)** + 该哈希值是基于一系列文件内容计算得出的,这些文件是在获取URL后并应用了路径规则后得到的。 + 这个字段是最重要的;一个包是的唯一性是由它的哈希值确定的,不同的 URL 可能对应同一个包。 + +## 多重哈希 + +在他们的网站上有一个很好的可视化展示,说明了这一过程: [多重哈希](https://multiformats.io/multihash/)。 + +![multihash 示意图](/images/zon-multihash.webp) + +因此 `build.zig.zon` 中的哈希字段不仅包含了摘要(digest),还包含了一些元数据(metadata)。但即使我们丢弃了头部信息,得到的结果仍与下载的 `tar` 包的 `sha256` 摘要不相符。而这就涉及到了包含规则的问题。 + +## 包含规则(inclusion rules) + +回到 [doc/build.zig.zon.md](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/doc/build.zig.zon.md) 文件,我们看到: + +> 这个计算的 hash 结果是在获取 URL 后,根据应用路径给出的包含规则,然后通过获得的文件目录内容计算出来。 + +那神秘的包含规则是什么呢?不幸的是,我又没找到这些内容的具体描述。唯一提到这些的地方是在 [ziglang/src/Package/Fetch.zig](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Fetch.zig#L1-L28) 文件的开头,但只能了解到无关文件被过滤后,哈希值是在剩余文件的基础上计算出来的结果。 + +幸好在代码中快速搜索后,我们找到了负责计算哈希的 `fetch` 任务的 [主函数](https://github.com/ziglang/zig/blob/9d64332a5959b4955fe1a1eac793b48932b4a8a8/src/main.zig#L6865)。 + +我们看到它调用了 [`runResource`](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Fetch.zig#L478-L485) 函数。路径字段从依赖的 `build.zig.zon` 中读取,并稍后用于创建某种过滤器。 + +这是我们一直在寻找的过滤器 `filter`。在这个结构的命名空间内定义了一个 `includePath` 函数,而它处理了所有那些包含规则。 + +```zig +/// sub_path is relative to the package root. +pub fn includePath(self: Filter, sub_path: []const u8) bool { + if (self.include_paths.count() == 0) return true; + if (self.include_paths.contains("")) return true; + if (self.include_paths.contains(".")) return true; + if (self.include_paths.contains(sub_path)) return true; + + // Check if any included paths are parent directories of sub_path. + var dirname = sub_path; + while (std.fs.path.dirname(dirname)) |next_dirname| { + if (self.include_paths.contains(next_dirname)) return true; + dirname = next_dirname; + } + + return false; +} +``` + +这个函数用于判断 `sub_path` 下的文件是否属于包的一部分。我们可以看到有三种特殊情况,文件会被认为是包的一部分: + +1. `include_paths` 为空 +2. `include_paths` 中含有空字符串 "" +3. `include_paths` 包含包的根目录 "." + +除此之外,这个函数会检查 `sub_path` 是否被明确列出,或者是已明确列出的目录的子目录。 + +## 计算哈希 + +现在我们知道了 `build.zig.zon` 的包含规则,也知道使用了 `SHA256` 算法。但我们仍然不知道实际的哈希结果是如何得到的。例如,它可能是通过将所有包含的文件内容输入哈希器来计算的。所以让我们再仔细看看,也许我们可以找到答案。 + +回到 `runResource` 函数,我们看到它调用了 [`computeHash`](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Fetch.zig#L1324) 函数,这看起来应该是我们感兴趣的主要内容(它顶部的注释已经无人维护,因为这里面会进行[文件删除](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Fetch.zig#L1383-L1385))。 + +在其中,我们偶然发现了这段[代码](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Fetch.zig#L1404-L1416): + +```zig +const hashed_file = try arena.create(HashedFile); +hashed_file.* = .{ + .fs_path = fs_path, + .normalized_path = try normalizePathAlloc(arena, entry_pkg_path), + .kind = kind, + .hash = undefined, // to be populated by the worker + .failure = undefined, // to be populated by the worker +}; +wait_group.start(); +try thread_pool.spawn(workerHashFile, .{ + root_dir, hashed_file, &wait_group, +}); +try all_files.append(hashed_file); +``` + +这里没有传递任何哈希对象,只传递了项目的根目录和一个指向 `HashedFile` 结构的指针。它有一个专门的 `hash` 字段。先前的猜想似乎不成立,因为哈希值是为单个文件存储的。为了更好地理解这个计算过程,顺着这条新线索看看后续。 + +跟踪 `workerHashFile`,我们看到它是 `hashFileFallible` 的一个简单包装,而后者看起来相当复杂。让我们来分解一下。 + +## 单个文件的哈希计算 + +首先,会进行一些初始化设置,其中创建并用规整后的文件路径初始化了一个新的哈希器实例: + +```zig +var buf: [8000]u8 = undefined; +var hasher = Manifest.Hash.init(.{}); +hasher.update(hashed_file.normalized_path); +``` + +然后我们根据我们正在哈希的文件类型进行切换。有两个分支: + +- 一个用于常规文件 +- 一个用于符号链接 + +首先来看看常规文件的情况: + +```zig +var file = try dir.openFile(hashed_file.fs_path, .{}); +defer file.close(); +// Hard-coded false executable bit: https://github.com/ziglang/zig/issues/17463 +hasher.update(&.{ 0, 0 }); +var file_header: FileHeader = .{}; +while (true) { + const bytes_read = try file.read(&buf); + if (bytes_read == 0) break; + hasher.update(buf[0..bytes_read]); + file_header.update(buf[0..bytes_read]); +} +``` + +首先,打开对应文件文件以便稍后读取其内容,这个符合预期,但紧接着我们放入了两个 null 字节。从阅读 [#17463](https://github.com/ziglang/zig/issues/17463) 来看,这似乎历史原因,为了进行历史兼容。无论如何,之后我们简单地循环读取文件数据的块,并将它们作为数据来计算哈希值。 + +现在来看看符号链接分支,这个更简单: + +```zig +const link_name = try dir.readLink(hashed_file.fs_path, &buf); +if (fs.path.sep != canonical_sep) { + // Package hashes are intended to be consistent across + // platforms which means we must normalize path separators + // inside symlinks. + normalizePath(link_name); +} +hasher.update(link_name); +``` + +首先进行路径分隔符的规整,保证不同平台一致,之后将符号链接的目标路径输入 `hasher`。在 `hashFileFallible` 函数最后,把计算出的哈希值赋值给 `HashedFile` 对象的 `hash` 字段。 + +## 组合哈希 + +尽管有了单个文件的哈希值,但我们仍不知道如何得到最终的哈希。幸运的是,曙光就在眼前。 + +下一步是确保我们有可复现的结果。 `HashedFile` 对象被存储在一个数组中,但文件系统遍历算法可能会改变,所以我们需要对那个数组进行排序。 + +```zig +std.mem.sortUnstable(*HashedFile, all_files.items, {}, HashedFile.lessThan); +``` + +最后,我们到达了将所有这些哈希[组合成一个的地方](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Fetch.zig#L1452-L1464): + +```zig +var hasher = Manifest.Hash.init(.{}); +var any_failures = false; +for (all_files.items) |hashed_file| { + hashed_file.failure catch |err| { + any_failures = true; + try eb.addRootErrorMessage(.{ + .msg = try eb.printString("unable to hash '{s}': {s}", .{ + hashed_file.fs_path, @errorName(err), + }), + }); + }; + hasher.update(&hashed_file.hash); +} +``` + +在这里我们看到所有计算出的哈希被一个接一个地输入到一个新的哈希器中。在 `computeHash` 的最后,我们返回 `hasher.finalResult()`,现在我们明白哈希值是如何获得的了。 + +## 最终多哈希值 + +现在我们有了一个 `SHA256` 摘要,可以最终返回到 `main.zig`,在那里我们调用 [`Package.Manifest.hexDigest(fetch.actual_hash)`](https://github.com/ziglang/zig/blob/9d64332a5959b4955fe1a1eac793b48932b4a8a8/src/Package/Manifest.zig#L174)。在那里,我们将多哈希头写入缓冲区,之后是我们的组合摘要。 + +顺便说一下,我们看到所有哈希头都是 `1220` 并非巧合。这是因为 `Zig` [硬编码了 SHA256](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Manifest.zig#L3) - 0x12,它有 32 字节的摘要 - 0x20。 + +# 总结 + +总结一下:最终哈希值是一个多哈希头 + `SHA256` 摘要。 + +这些摘要是包文件里的部分文件的 `SHA256` 摘要。这些摘要根据文件路径排序,并且对于普通文件和符号链接的计算方式不同。 + +这整个调查实际上是我尝试编写一个输出与 Zig 相同哈希的 shell 脚本的结果。如果你感兴趣,可以在这里阅读它:https://gist.github.com/michalsieron/a07da4216d6c5e69b32f2993c61af1b7。 + +在实验这个之后,我有一个想法,我很惊讶 Zig 没有检查 `build.zig.zon` 中列出的所有文件是否存在。但这可能是另一天的话题了。 + +# 译者注 + +在使用本地包时,可以使用下面的命令进行 hash 问题的排查: + +```bash + (main)$ zig fetch --debug-hash . +file: 001f530a93f06d8ad8339ec2f60f17ff9ff0ae29ceaed36942a8bc96ba9d7e26: LICENSE +file: ba267af6281ec7b52f90357cdf280e2bf974a0b493314705f18983d4fb818e90: build.zig +file: b2f7c2d2571a10f289112dbb16599ff88cc9709a7492fe42b76da92b9420ab18: build.zig.zon +file: 614020c9dc5abae8a2a0943030a4e1ddd1ab74a5b40e78896a9db24a353338e1: libs/.DS_Store +file: 673fd9dc257504fab812c8a7e79ec0cc90f83d426392dc8f1b990149db06e95f: libs/curl.zig +file: ebdf40a5c1308661cbaf1d69c3caf439f848e9a506029474f4e4f361e36fc836: libs/mbedtls.zig +file: e4e3a40d8e9670984f387936fcdeb9a2cbe86ee70ab898ba3837d922e5c14125: libs/zlib.zig +file: 6ba4206baa82168198e7c869ce01f002ee7e3cd67c200f5c603fa9c17201333f: src/Easy.zig +file: aabb5cedf021c6c7720103dd5e5a2088eeff36823a0ac3303fb965ca16012a8c: src/Multi.zig +file: 5f254a82524e9d625f7cf2ee80a601da642466d9e7ff764afad480529f51222a: src/errors.zig +file: a77c3ca16664533409c4618f54a43f9039427431894d09b03490a91a10864a4c: src/root.zig +file: 7b398ebd7ddb3ae30ff1ff1010445b3ed1f252db46608b6a6bd9aace233bc1a4: src/util.zig +1220110dc58ece4168ae3b2a0863c8676f8842bbbac763ad30e6ed1e2b68d973d615 +``` + +此外,社区已经有人把 multihash 的算法实现独立成一个单独的包,便于计算一个包的 hash 值: + +- https://github.com/Calder-Ty/multihash diff --git a/content/post/2024-05-24-interface-idioms.smd b/content/post/2024-05-24-interface-idioms.smd new file mode 100644 index 0000000..8b6f7c5 --- /dev/null +++ b/content/post/2024-05-24-interface-idioms.smd @@ -0,0 +1,446 @@ +--- +.title = "Zig 标准库中的实现接口的惯用法与模式", +.date = @date("2024-05-24T23:21:12-05:00"), +.author = "Rui Chen", +.layout = "post.shtml", +.draft = false, +--- + +> 原文链接: https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj + +# 引言 + +在 Java 和 Go 中,可以使用“接口”(一组方法或方法集)定义基于行为的抽象。通常接口包含所谓的虚表(`vtable`) +以实现动态分派。Zig 允许在结构体、枚举、联合和不透明类型中声明函数和方法,尽管 Zig 尚未支持接口作为一种语言特性。 +Zig 标准库应用了一些代码习语或模式以达到类似效果。 + +类似于其他语言中的接口,Zig 的代码习语和模式实现了: + +- 在编译时对实例/对象方法与接口类型进行类型检查, +- 在运行时进行动态分派。 + +这里有一些显著的不同: + +- 在 Go 中,接口的定义与实现是独立的。可以在任何位置给一个类型实现新接口,只需保证其方法签名与新接口一致即可。无需像在 Java 中那样,需要回过头去修改类型定义,来实现新的接口。 +- Go 的接口只包含用于动态分派的 `vtab`,并且推荐 vtable 中方法即可能少 ,例如 `io.Reader` 和 `io.Writer`只有一个方法。 + 常见的工具函数如`io.Copy`、`CopyN`、`ReadFull`、`ReadAtLeast` 等,作为包函数提供,内部使用这些小接口。 + 与之相比,Zig 的接口,如 `std.mem.Allocator`,同时包含 `vtable` 和一些工具方法,因此方法会多一些。 + +以下是 Zig 的代码习语/模式在动态分派方面的学习笔记,代码摘自 Zig 标准库并以简单示例重录。为了专注于 vtab/动态分派,工具方法被移除, +代码稍作修改以适应 Go 中不依赖具体类型的“小”接口模式。 + +完整代码位于[此仓库](https://github.com/yglcode/zig_interfaces),你可以使用 `zig test interfaces.zig` 运行它。 + +# 背景设定 + +让我们使用经典的面向对象编程示例,创建一些形状:点(`Point`)、盒子(`Box`)和圆(`Circle`)。 + +```zig +const Point = struct { + x: i32 = 0, + y: i32 = 0, + pub fn move(self: *Point, dx: i32, dy: i32) void { + self.x += dx; + self.y += dy; + } + pub fn draw(self: *Point) void { + print("point@<{d},{d}>\n", .{ self.x, self.y }); + } +}; + +const Box = struct { + p1: Point, + p2: Point, + pub fn init(p1: Point, p2: Point) Box { + return .{ .p1 = p1, .p2 = p2 }; + } + pub fn move(self: *Box, dx: i32, dy: i32) void { + ...... + } + pub fn draw(self: *Box) void { + ...... + } +}; + +const Circle = struct { + center: Point, + radius: i32, + pub fn init(c: Point, r: i32) Circle { + return .{ .center = c, .radius = r }; + } + pub fn move(self: *Circle, dx: i32, dy: i32) void { + self.center.move(dx, dy); + } + pub fn draw(self: *Circle) void { + ...... + } +}; + +// 创建一组“形状”用于测试 +fn init_data() struct { point: Point, box: Box, circle: Circle } { + return .{ + .point = Point{}, + .box = Box.init(Point{}, Point{ .x = 2, .y = 3 }), + .circle = Circle.init(Point{}, 5), + }; +} +``` + +# 接口1:枚举标签联合 + +Loris Cro 在[“使用 Zig 0.10.0 轻松实现接口”](https://zig.news/kristoff/easy-interfaces-with-zig-0100-2hc5) +中介绍了使用枚举标签联合作为接口的方法。这是最简单的解决方案,尽管你必须在联合类型中显式列出所有“实现”该接口的变体类型。 + +```zig +const Shape1 = union(enum) { + point: *Point, + box: *Box, + circle: *Circle, + pub fn move(self: Shape1, dx: i32, dy: i32) void { + switch (self) { + inline else => |s| s.move(dx, dy), + } + } + pub fn draw(self: Shape1) void { + switch (self) { + inline else => |s| s.draw(), + } + } +}; +``` + +我们可以如下测试: + +```zig +test "union_as_intf" { + var data = init_data(); + var shapes = [_]Shape1{ + .{ .point = &data.point }, + .{ .box = &data.box }, + .{ .circle = &data.circle }, + }; + for (shapes) |s| { + s.move(11, 22); + s.draw(); + } +} +``` + +# 接口2:vtable 和动态分派的第一种实现 + +Zig 已从最初基于嵌入式 `vtab` 和 `#fieldParentPtr()` 的动态分派切换到基于“胖指针”接口的以下模式; +请查阅此文章了解更多细节[“Allocgate 将在 Zig 0.9 中到来...”](https://pithlessly.github.io/allocgate.html)。 + +接口 `std.mem.Allocator` 使用了这种模式,所有标准分配器,如 `std.heap.[ArenaAllocator, GeneralPurposeAllocator, ...]` 都有一个方法 `allocator() Allocator` 来暴露这个接口。 +以下代码稍作改动,将接口从实现中分离出来。 + +```zig +const Shape2 = struct { + // 定义接口字段: ptr,vtab + ptr: *anyopaque, //ptr to instance + vtab: *const VTab, //ptr to vtab + const VTab = struct { + draw: *const fn (ptr: *anyopaque) void, + move: *const fn (ptr: *anyopaque, dx: i32, dy: i32) void, + }; + + // 定义封装 vtable 调用的接口方法 + pub fn draw(self: Shape2) void { + self.vtab.draw(self.ptr); + } + pub fn move(self: Shape2, dx: i32, dy: i32) void { + self.vtab.move(self.ptr, dx, dy); + } + + // 将具体实现类型/对象转换为接口 + pub fn init(obj: anytype) Shape2 { + const Ptr = @TypeOf(obj); + const PtrInfo = @typeInfo(Ptr); + assert(PtrInfo == .Pointer); // 必须是指针 + assert(PtrInfo.Pointer.size == .One); // 必须是单项指针 + assert(@typeInfo(PtrInfo.Pointer.child) == .Struct); // 必须指向一个结构体 + const alignment = PtrInfo.Pointer.alignment; + const impl = struct { + fn draw(ptr: *anyopaque) void { + const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); + self.draw(); + } + fn move(ptr: *anyopaque, dx: i32, dy: i32) void { + const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); + self.move(dx, dy); + } + }; + return .{ + .ptr = obj, + .vtab = &.{ + .draw = impl.draw, + .move = impl.move, + }, + }; + } +}; +``` + +我们可以如下测试: + +```zig +test "vtab1_as_intf" { + var data = init_data(); + var shapes = [_]Shape2{ + Shape2.init(&data.point), + Shape2.init(&data.box), + Shape2.init(&data.circle), + }; + for (shapes) |s| { + s.move(11, 22); + s.draw(); + } +} +``` + +# 接口3:vtable 和动态分派的第二种实现 + +在上述第一种实现中,通过 `Shape2.init()` 将 `Box` “转换”为接口 `Shape2` 时,会对 `box` 实例进行类型检查, +以确保其实现了 `Shape2` 的方法(包括名称的匹配签名)。第二种实现中有两个变化: + +- `vtable` 内联在接口结构中(可能的缺点是,接口大小增加)。 +- 需要根据接口进行类型检查的方法被显式地作为函数指针传入,这可能允许传入不同的方法,只要它们具有相同的参数/返回类型。 + 例如,如果 `Box` 有额外的方法,`stopAt(i32,i32)` 或甚至 `scale(i32,i32)`,我们可以将它们替换为 `move()`。 + 接口 `std.rand.Random` 和所有 `std.rand.[Pcg, Sfc64, ...]` 使用这种模式。 + +```zig +const Shape3 = struct { + // 指向实例的 ptr + ptr: *anyopaque, + // 内联 vtable + drawFnPtr: *const fn (ptr: *anyopaque) void, + moveFnPtr: *const fn (ptr: *anyopaque, dx: i32, dy: i32) void, + + pub fn init( + obj: anytype, + comptime drawFn: fn (ptr: @TypeOf(obj)) void, + comptime moveFn: fn (ptr: @TypeOf(obj), dx: i32, dy: i32) void, + ) Shape3 { + const Ptr = @TypeOf(obj); + assert(@typeInfo(Ptr) == .Pointer); // 必须是指针 + assert(@typeInfo(Ptr).Pointer.size == .One); // 必须是单项指针 + assert(@typeInfo(@typeInfo(Ptr).Pointer.child) == .Struct); // 必须指向一个结构体 + const alignment = @typeInfo(Ptr).Pointer.alignment; + const impl = struct { + fn draw(ptr: *anyopaque) void { + const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); + drawFn(self); + } + fn move(ptr: *anyopaque, dx: i32, dy: i32) void { + const self = @ptrCast(Ptr, @alignCast(alignment, ptr)); + moveFn(self, dx, dy); + } + }; + + return .{ + .ptr = obj, + .drawFnPtr = impl.draw, + .moveFnPtr = impl.move, + }; + } + + // 定义封装 vtable 函数指针的接口方法 + pub fn draw(self: Shape3) void { + self.drawFnPtr(self.ptr); + } + pub fn move(self: Shape3, dx: i32, dy: i32) void { + self.moveFnPtr(self.ptr, dx, dy); + } +}; +``` + +我们可以如下测试: + +```zig +test "vtab2_as_intf" { + var data = init_data(); + var shapes = [_]Shape3{ + Shape3.init(&data.point, Point.draw, Point.move), + Shape3.init(&data.box, Box.draw, Box.move), + Shape3.init(&data.circle, Circle.draw, Circle.move), + }; + for (shapes) |s| { + s.move(11, 22); + s.draw(); + } +} +``` + +# 接口4:使用嵌入式 vtab 和 @fieldParentPtr() 的原始动态分派 + +接口 `std.build.Step` 和所有构建步骤 `std.build.[RunStep, FmtStep, ...]` 仍然使用这种模式。 + +```zig +// 定义接口/vtab +const Shape4 = struct { + drawFn: *const fn (ptr: *Shape4) void, + moveFn: *const fn (ptr: *Shape4, dx: i32, dy: i32) void, + // 定义封装 vtab 函数的接口方法 + pub fn draw(self: *Shape4) void { + self.drawFn(self); + } + pub fn move(self: *Shape4, dx: i32, dy: i32) void { + self.moveFn(self, dx, dy); + } +}; + +// 嵌入 vtab 并将 vtab 函数定义为方法的封装 +const Circle4 = struct { + center: Point, + radius: i32, + shape: Shape4, // 嵌入 vtab + pub fn init(c: Point, r: i32) Circle4 { + // 定义接口封装函数 + const impl = struct { + pub fn draw(ptr: *Shape4) void { + const self = @fieldParentPtr(Circle4, "shape", ptr); + self.draw(); + } + pub fn move(ptr: *Shape4, dx: i32, dy: i32) void { + const self = @fieldParentPtr(Circle4, "shape", ptr); + self.move(dx, dy); + } + }; + return .{ + .center = c, + .radius = r, + .shape = .{ .moveFn = impl.move, .drawFn = impl.draw }, + }; + } + // 以下是方法 + pub fn move(self: *Circle4, dx: i32, dy: i32) void { + self.center.move(dx, dy); + } + pub fn draw(self: *Circle4) void { + print("circle@<{d},{d}>radius:{d}\n", .{ self.center.x, self.center.y, self.radius }); + } +}; + +// 在结构体上直接嵌入 vtab 并定义 vtab 函数 +const Box4 = struct { + p1: Point, + p2: Point, + shape: Shape4, // 嵌入 vtab + pub fn init(p1: Point, p2: Point) Box4 { + return .{ + .p1 = p1, + .p2 = p2, + .shape = .{ .moveFn = move, .drawFn = draw }, + }; + } + // 以下是 vtab 函数,不是方法 + pub fn move(ptr: *Shape4, dx: i32, dy: i32) void { + const self = @fieldParentPtr(Box4, "shape", ptr); + self.p1.move(dx, dy); + self.p2.move(dx, dy); + } + pub fn draw(ptr: *Shape4) void { + const self = @fieldParentPtr(Box4, "shape", ptr); + print("box@<{d},{d}>-<{d},{d}>\n", .{ self.p1.x, self.p1.y, self.p2.x, self.p2.y }); + } +}; +``` + +我们可以如下测试: + +```zig +test "vtab3_embedded_in_struct" { + var box = Box4.init(Point{}, Point{ .x = 2, .y = 3 }); + var circle = Circle4.init(Point{}, 5); + + var shapes = [_]*Shape4{ + &box.shape, + &circle.shape, + }; + for (shapes) |s| { + s.move(11, 22); + s.draw(); + } +} +``` + +# 接口5:编译时的泛型接口 + +所有上述接口都侧重于 `vtab` 和动态分派:接口值将隐藏其持有的具体值的类型。因此,你可以将这些接口值放入数组中并统一处理。 + +通过 Zig 的编译时计算,你可以定义泛型算法,它可以与提供代码函数体所需的方法或操作符的任何类型一起工作。例如, +我们可以定义一个泛型算法: + +```zig +fn update_graphics(shape: anytype, dx: i32, dy: i32) void { + shape.move(dx, dy); + shape.draw(); +} +``` + +如上所示,“shape”可以是任何类型,只要它提供 `move()` 和 `draw()` 方法。所有类型检查都发生在编译时,并且没有动态分派。 + +接下来,我们可以定义一个泛型接口,捕获泛型算法所需的方法;我们可以用它来适应具有不同方法名称的某些类型/实例到所需的 API。 + +接口 `std.io.[Reader, Writer]` 以及 `std.fifo` 和 `std.fs.File` 使用这种模式。 + +由于这些泛型接口没有擦除其持有的值的类型信息,它们是不同的类型。因此,你不能将它们放入数组中以统一处理。 + +```zig +pub fn Shape5( + comptime Pointer: type, + comptime drawFn: *const fn (ptr: Pointer) void, + comptime moveFn: *const fn (ptr: Pointer, dx: i32, dy: i32) void, +) type { + return struct { + ptr: Pointer, + const Self = @This(); + pub fn init(p: Pointer) Self { + return .{ .ptr = p }; + } + // 封装传入的函数/方法的接口方法 + pub fn draw(self: Self) void { + drawFn(self.ptr); + } + pub fn move(self: Self, dx: i32, dy: i32) void { + moveFn(self.ptr, dx, dy); + } + }; +} + +// 一种泛型算法使用鸭子类型/静态分派。 +// 注意:形状可以是提供 `move()`/`draw()` 的“任何类型” +fn update_graphics(shape: anytype, dx: i32, dy: i32) void { + shape.move(dx, dy); + shape.draw(); +} + +// 定义一个具有相似但不同方法的 `TextArea` +const TextArea = struct { + position: Point, + text: []const u8, + pub fn init(pos: Point, txt: []const u8) TextArea { + return .{ .position = pos, .text = txt }; + } + pub fn relocate(self: *TextArea, dx: i32, dy: i32) void { + self.position.move(dx, dy); + } + pub fn display(self: *TextArea) void { + print("text@<{d},{d}>:{s}\n", .{ self.position.x, self.position.y, self.text }); + } +}; +``` + +我们可以如下测试: + +```zig +test "generic_interface" { + var box = Box.init(Point{}, Point{ .x = 2, .y = 3 }); + // 将泛型算法直接应用于匹配类型 + update_graphics(&box, 11, 22); + var textarea = TextArea.init(Point{}, "hello zig!"); + // 使用泛型接口来适应不匹配的类型 + var drawText = Shape5(*TextArea, TextArea.display, TextArea.relocate).init(&textarea); + update_graphics(drawText, 4, 5); +} +``` diff --git a/content/post/2024-06-10-zig-hashmap-1.smd b/content/post/2024-06-10-zig-hashmap-1.smd new file mode 100644 index 0000000..ef350dc --- /dev/null +++ b/content/post/2024-06-10-zig-hashmap-1.smd @@ -0,0 +1,388 @@ +--- +.title = "HashMap 原理介绍上篇", +.date = @date("2024-06-10T07:57:05.138Z"), +.author = "Wenxuan Feng", +.layout = "post.shtml", +.draft = false, +--- + +> 阅读这篇文章的前提是了解 [Zig 的范型实现](https://www.openmymind.net/learning_zig/generics/) + +如大多数哈希映射实现一样,Zig 的 `std.HashMap` 依赖于两个函数:`hash(key: K) u64` 和 `eql(key_a: K, key_b: K) bool`。其中,哈希函数接收一个键并返回一个无符号的64位整数作为哈希码。相同的关键字总是会返回相同的哈希码。然而,为了处理不同的键可能生成相同哈希码的情况(即碰撞),我们还需要 `eql` 函数来确定两个键是否相等。 + +这是一些标准做法,但Zig的实现有一些特定的细节值得关注。尤其是考虑到标准库中包含多种哈希映射类型以及文档似乎不完整且令人困惑这一点。具体来说,有六种哈希映射变体:`std.HashMap`, `std.HashMapUnmanaged`, `std.AutoHashMap`, `std.AutoHashMapUnmanaged`, `std.StringHashMap`, 和 `std.StringHashMapUnmanaged`。 + +`std.HashMapUnmanaged` 包含了实现的主要部分。其他五个都是对它的简单包装。由于这些变体通过一个名为“unmanaged”的字段进行包装,因此这五种类型的文档处理不清晰。 + +如果查看 `std.HashMap` 的 `put` 方法,会发现一个经常重复的应用模式: + +```zig +pub fn put(self: *Self, key: K, value: V) Allocator.Error!void { + return self.unmanaged.putContext(self.allocator, key, value, self.ctx); +} +``` + +正如我所说,大部分繁重的工作都由 `std.HashMapUnmanaged` 完成,其他变体通过一个名为 `unmanaged` 的字段对其进行封装。 + +# Unmanaged + +在Zig标准库中随处可见的类型命名约定是 `unmanaged`。这种命名方式表明所涉及的类型不维护 `allocator`。任何需要分配内存的方法都会显式地将 `allocator` 作为参数传递。要实际看到这一点,可以考虑下面这个链表的例子: + +```zig +pub fn LinkedList(comptime T: type) type { + return struct { + head: ?*Node = null, + allocator: Allocator, + + const Self = @This(); + + pub fn init(allocator: Allocator) Self { + return .{ + .allocator = allocator, + }; + } + + pub fn deinit(self: Self) void { + var node = self.head; + while (node) |n| { + node = n.next; + self.allocator.destroy(n); + } + } + + pub fn append(self: *Self, value: T) !void { + const node = try self.allocator.create(Node); + node.value = value; + const h = self.head orelse { + node.next = null; + self.head = node; + return; + }; + node.next = h; + self.head = node; + } + + pub const Node = struct { + value: T, + next: ?*Node, + }; + }; +} +``` + +我们的初始化函数接受并存储一个 `std.mem.Allocator`。这个分配器随后将在 append 和 deinit 操作中根据需要使用。这在 Zig 中是一个常见的模式。上述 `unmanaged` 版本只有细微的差别: + +```zig +pub fn LinkedListUnmanaged(comptime T: type) type { + return struct { + head: ?*Node = null, + + const Self = @This(); + + pub fn deinit(self: Self, allocator: Allocator) void { + var node = self.head; + while (node) |n| { + node = n.next; + allocator.destroy(n); + } + } + + pub fn append(self: *Self, allocator: Allocator, value: T) !void { + const node = try allocator.create(Node); + // .. same as above + } + + // Node is the same as above + pub const Node = struct {...} + }; +} +``` + +整体而言,代码已经是高质量的,上面的更改是细微优化的一部分。 +我们不再有一个 `allocator` 字段。`append` 和 `deinit` 函数都多了一个额外的参数:`allocator`。因为我们不再需要存储 `allocator`,我们能够仅用默认值初始化 `LinkedListUnmanaged(T)`(即 `head: ?*Node = null`),并且能够完全移除 `init` 函数。这不是未管理类型的要求,但这是常见的做法。要创建一个 `LinkedListUnmanaged(i32)`,你可以这样做: + +```zig +var ll = LinkedListUnmanaged(i32){}; +``` + +这看起来有点神秘,但这是标准的 Zig。`LinkedListUnmanaged(i32)` 返回一个类型,所以上面的做法和执行 `var user = User{}` 并依赖 `User` 的默认字段值没有区别。 + +你可能会好奇 `unmanaged` 类型有什么用?但在我们回答这个问题之前,让我们考虑一下提供我们的 LinkedList 的 `managed` 和 `unmanaged` 版本有多容易。我们保持我们的 `LinkedListUnmanaged` 如原样,并改变我们的 `LinkedList` 来包装它: + +```zig +pub fn LinkedList(comptime T: type) type { + return struct { + allocator: Allocator, + unmanaged: LinkedListUnmanaged(T), + + const Self = @This(); + + pub fn init(allocator: Allocator) Self { + return .{ + .unmanaged = .{}, + .allocator = allocator, + }; + } + + pub fn deinit(self: Self) void { + self.unmanaged.deinit(self.allocator); + } + + pub fn append(self: *Self, value: T) !void { + return self.unmanaged.append(self.allocator, value); + } + + pub const Node = LinkedListUnmanaged(T).Node; + }; +} +``` + +这种简单的组合方式,正如我们上面所见,与各种哈希映射类型包装 `std.HashMapUnmanaged` 的方式相同。 + +`unmanaged` 类型有几个好处。最重要的是它们更加明确。与知道像 `LinkList(T)` 这样的类型可能在某个时刻需要分配内存不同,未管理变体的明确 API 标识了进行分配/释放的特定方法。这可以帮助减少意外并为调用者提供更大的控制权。未管理类型的次要好处是它们通过不引用分配器节省了一些内存。一些应用可能需要存储成千上万甚至更多这样的结构,在这种情况下,这种节省可以累积起来。 + +为了简化,本文的其余部分不会再提到 `unmanaged`。我们看到关于 `StringHashMap` 或 `AutoHashMap` 或 `HashMap` 的任何内容同样适用于它们的 Unmanaged 变体。 + +# HashMap 与 AutoHashMap + +std.HashMap 是一个泛型类型,它接受两个类型参数:键的类型和值的类型。正如我们所见,哈希映射需要两个函数:hash 和 eql。这两个函数合起来被称为“上下文(context)”。这两个函数都作用于键,并且没有一个单一的 hash 或 eql 函数适用于所有类型。例如,对于整数键,eql 将是 `a_key == b_key`;而对于 `[]const u8` 键,我们希望使用 `std.mem.eql(u8, a_key, b_key)`。 + +当我们使用 std.HashMap 时,我们需要提供上下文(这两个函数)。我们不久后将讨论这一点,但现在我们可以依赖 std.AutoHashMap,它为我们自动生成这些函数。可能会让你惊讶的是,AutoHashMap 甚至可以为更复杂的键生成上下文。以下操作是有效的: +以下是修正后的代码: + +```zig +const std = @import("std"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator{}.init(); + const allocator = gpa.allocator(); + + var h = std.AutoHashMap(User, i32).init(allocator); + try h.put(User{ id = 3, state = .active }, 9001); + defer h.deinit(); + + const User = struct { + id: i32, + state: State, + + const State = enum { active, pending }; + }; +} + +const User = struct { + id: i32, + state: State, + login_ids: []i32, // You intended to use an array here instead of a slice. + ... +}; +``` + +修改后的代码中,我修正了 `User` 结构体内部的 `login_ids` 从切片(`[]T`)改为了数组 (`[N]T`)。在 Zig 中,使用数组可以避免与切片相关的不确定性和模糊性问题。 + +此外,我还优化了 `std.heap.GeneralPurposeAllocator` 的初始化方式。原本的 `.{}` 是不必要的,并且已经更新至更简洁的形式。 +你会被原谅,如果你认为 `StringHashMap(V)` 是 `AutoHashMap([], V)` 的别名。但正如我们刚看到的,`AutoHashMap` 不支持切片键。我们可以确认这一点。尝试运行: + +```zig +const std = @import("std"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + var h = std.AutoHashMap([]const u8, i32).init(allocator); + try h.put("over", 9000); + defer h.deinit(); +} +``` + +得到下面的错误: + +> error: `std.auto_hash.autoHash` does not allow slices here (`[]const u8`) because the intent is unclear. Consider using `std.StringHashMap` for hashing the contents of `[]const u8`. Alternatively, consider using `std.auto_hash.hash` or providing your own hash function instead. + +正如我之前所说,问题不是切片不能被哈希或比较,而是有些情况下,切片只有在引用相同内存时才会被认为是相等的,而另一些情况下,两个切片如果它们的元素相同就会被认为是相等的。但是,对于字符串,大多数人期望“teg”无论存储在哪里都应该等于“teg”。 + +```zig +const std = @import("std"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + const name1: []const u8 = &.{'T', 'e', 'g'}; + const name2 = try allocator.dupe(u8, name1); + + const eql1 = std.meta.eql(name1, name2); + const eql2 = std.mem.eql(u8, name1, name2); + std.debug.print("{any}\n{any}", .{eql1, eql2}); +} +``` + +上述程序打印“false”,然后打印“true”。`std.meta.eql`使用 `a.ptr == b.ptr` 和 `a.len == b.len` 来比较指针。但具体到字符串,大多数程序员可能期望 `std.mem.eql` 的行为,它比较字符串内部的字节。 + +因此,就像 `AutoHashMap` 包装了带有自动生成上下文的 `HashMap` 一样,`StringHashMap` 也包装了带有字符串特定上下文的 `HashMap`。我们将更仔细地看上下文,但这里是 `StringHashMap` 使用的上下文: + +```zig +pub const StringContext = struct { + pub fn hash(self: @This(), s: []const u8) u64 { + _ = self; + return std.hash.Wyhash.hash(0, s); + } + pub fn eql(self: @This(), a: []const u8, b: []const u8) bool { + _ = self; + return std.mem.eql(u8, a, b); + } +}; +``` + +# 自定义上下文 + +我们将在第一部分结束时,直接使用 `HashMap`,这意味着提供我们自己的上下文。我们将从一个简单的例子开始:为不区分大小写的 ASCII 字符串创建一个 `HashMap`。我们希望以下内容输出:`Goku = 9000`。请注意,虽然我们使用键 `GOKU` 进行插入,但我们使用“goku”进行获取: + +```zig +const std = @import("std"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + var h = std.HashMap([]const u8, i32, CaseInsensitiveContext, std.hash_map.default_max_load_percentage).init(allocator); + defer h.deinit(); + try h.put("GOKU", 9000); + std.debug.print("Goku = {d}\n", .{h.get("goku").?}); +} +``` + +与只需要值类型的 `StringHashMap` 泛型或需要键和值类型的 `AutoHashMap` 不同,`HashMap` 需要键类型、值类型、上下文类型和填充因子。我们在此未涉及填充因子;在上面我们使用了 Zig 的默认填充因子(80%)。我们的兴趣点在于 `CaseInsensitiveContext` 类型及其实现: + +```zig +const CaseInsensitiveContext = struct { + pub fn hash(_: CaseInsensitiveContext, s: []const u8) u64 { + var key = s; + var buf: [64]u8 = undefined; + var h = std.hash.Wyhash.init(0); + while (key.len >= 64) { + const lower = std.ascii.lowerString(buf[0..], key[0..64]); + h.update(lower); + key = key[64..]; + } + + if (key.len > 0) { + const lower = std.ascii.lowerString(buf[0..key.len], key); + h.update(lower); + } + return h.final(); + } + + pub fn eql(_: CaseInsensitiveContext, a: []const u8, b: []const u8) bool { + return std.ascii.eqlIgnoreCase(a, b); + } +}; +``` + +这两个函数的第一个参数是上下文本身的实例。这允许更高级的模式,其中上下文可能有状态。但在许多情况下,它并未使用。 + +我们的 `eql` 函数使用现有的 `std.ascii.eqlIgnoreCase` 函数以不区分大小写的方式比较两个键。很直观。 + +我们的 `hash` 函数可以分为两部分。第一部分是将键转换为小写。如果我们希望“goku”和“GOKU”被视为相等,我们的哈希函数必须为两者返回相同的哈希码。 +我们以 64 字节为一批,以避免分配缓冲区来保存小写值。之所以能做到这一点,是因为我们的散列函数可以使用新字节进行更新(这很常见)。 + +这引出了第二部分,什么是 `std.hash.Wyhash`?当谈到哈希表的哈希算法时(不同于加密哈希算法),我们需要考虑一些属性,例如性能(每次操作哈希表都需要哈希键),均匀分布(如果我们的哈希函数返回 `u64`,那么一组随机输入应该在该范围内均匀分布)和碰撞抗性(不同的值可能会产生相同的哈希码,但发生的次数越少越好)。有许多算法,一些专门用于特定输入(例如短字符串),一些专为特定硬件设计。`WyHash` 是一种流行的选择,适用于许多输入和特征。你基本上将字节输入,一旦完成,就会得到一个 `u64`(或取决于版本的 `u32`)。 + +```zig +const std = @import("std"); + +pub fn main() !void { + { + const name = "Teg"; + + var h = std.hash.Wyhash.init(0); + h.update(name); + std.debug.print("{d}\n", .{h.final()}); + } + + { + const name = "Teg"; + const err = @intFromError(error.OutOfMemory); + + var h = std.hash.Wyhash.init(0); + h.update(name); + h.update(std.mem.asBytes(&err)); + std.debug.print("{d}\n", .{h.final()}); + } +} +``` + +这段代码输出: `17623169834704516898`,接着是 `7758855794693669122`。这些数字不应该有任何意义。目标只是展示如何将数据输入我们的哈希函数以生成哈希码。 + +让我们看另一个例子。假设我们有一个 `User`,我们希望用它作为哈希表中的键: + +```zig +const User = struct { + id: u32, + name: []const u8, +}; +``` + +我们不能使用 `AutoHashMap`,因为 `name` 不支持切片。示例如下: + +```zig +const std = @import("std"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + var h = std.HashMap(User, i32, User.HashContext, std.hash_map.default_max_load_percentage).init(allocator); + defer h.deinit(); + try h.put(.{.id = 1, .name = "Teg"}, 100); + try h.put(.{.id = 2, .name = "Duncan"}, 200); + + std.debug.print("{d}\n", .{h.get(.{.id = 1, .name = "Teg"}).?}); + std.debug.print("{d}\n", .{h.get(.{.id = 2, .name = "Duncan"}).?}); +} + +const User = struct { + id: u32, + name: []const u8, + + pub const HashContext = struct { + pub fn hash(_: HashContext, u: User) u64 { + // TODO + } + + pub fn eql(_: HashContext, a: User, b: User) bool { + // TODO + } + }; +}; +``` + +我们需要实现 `hash` 和 `eql` 函数。`eql`,通常很直观: + +```zig +pub fn eql(_: HashContext, a: User, b: User) bool { + if (a.id != b.id) return false; + return std.mem.eql(u8, a.name, b.name); +} +``` + +如果你看过我们的其他哈希示例,你可能会想到它的实现: + +```zig +pub fn hash(_: HashContext, u: User) u64 { + var h = std.hash.Wyhash.init(0); + h.update(u.name); + h.update(std.mem.asBytes(&u.id)); + return h.final(); +} +``` + +插入这两个函数,以上示例应该可以工作。 + +# 结论 + +希望你现在对 Zig 的哈希表的实现以及如何在代码中利用它们有了更好的理解。在大多数情况下,`std.StringHashMap` 或 `std.AutoHashMap` 就足够了。但知道 `*Unmanaged` 变体的存在和目的,以及更通用的 `std.HashMap`,可能会派上用场。如果没有其他用途,现在文档和它们的实现应该更容易理解了。 + +在下一部分,我们将深入探讨哈希表的键和值,它们是如何存储和管理的。 + +> 原文地址: https://www.openmymind.net/Zigs-HashMap-Part-1/ diff --git a/content/post/2024-06-11-zig-hashmap-2.smd b/content/post/2024-06-11-zig-hashmap-2.smd new file mode 100644 index 0000000..3216dcb --- /dev/null +++ b/content/post/2024-06-11-zig-hashmap-2.smd @@ -0,0 +1,374 @@ +--- +.title = "HashMap 原理介绍下篇", +.date = @date("2024-06-11T07:57:06.138Z"), +.author = "Wenxuan Feng", +.layout = "post.shtml", +.draft = false, +--- + +在[第一部分](2024-06-10-zig-hashmap-1)中,我们探讨了六种 `HashMap` 变体之间的关系以及每种变体为开发人员提供的不同功能。我们主要关注如何为各种数据类型定义和初始化 `HashMap`,并讨论了当 `StringHashMap` 或 `AutoHashMap` 不支持的类型时使用自定义 `hash` 和 `eql` 函数的重要性。在这篇文章中,我们将更深入地研究键和值的存储、访问方式以及我们在它们生命周期管理中的责任。 + +Zig 的哈希表内部采用两个切片结构:一个用于存放键(key),另一个用于存储对应的值(value)。通过应用哈希函数计算得到的哈希码被用来在这些数组中定位条目。从基础代码出发,比如: + +```zig +var lookup = std.StringHashMap(i32).init(allocator); +defer lookup.deinit(); + +try lookup.put("Goku", 9001); +try lookup.put("Paul", 1234); +``` + +这样的操作在哈希表中形成了一个类似如下的可视化表示: + +``` +keys: values: + -------- -------- + | Paul | | 1234 | @mod(hash("Paul"), 5) == 0 + -------- -------- + | | | | + -------- -------- + | | | | + -------- -------- + | Goku | | 9001 | @mod(hash("Goku"), 5) == 3 + -------- -------- + | | | | + -------- -------- +``` + +当我们使用模运算(如 `@mod`)将哈希码映射到数组中一个固定数量的槽位上时,我们就有了条目的理想位置。这里的"理想"是指哈希函数可能会为不同的键生成相同的哈希值;在计算时,通过数组大小进行取模有助于处理这种碰撞情况。然而,在忽略可能的冲突前提下,以上就是我们当前哈希表的基本视图。 + +一旦哈希表被填满到一定程度(如第一部分中提到,Zig 的默认填充因子为 80%),它就需要进行扩展来容纳更多值,同时保持常数时间性能的查找操作。哈希表的扩展过程类似于动态数组的扩容,我们分配一个新数组,并将原始数组中的值复制到新数组(通常会增加原数组大小的两倍作为简单算法)。然而,在处理哈希表时,简单的键值对复制是不够的。因为我们不能使用一种哈希方法如 `@mod(hash("Goku"), 5)` 并期望在另一个不同的哈希方法下找到相同的条目如 `@mod(hash("Goku"), 10)`(请注意,因为数组大小已增加,5 变成了 10)。 + +这个基本的可视化表示将贯穿本文大部分内容,并且不断强调条目的位置需要保持一致性和可预测性。即使哈希表需要在增长时从一个底层数组移动到另一个(即当填充因子达到一定阈值并要求扩大以容纳更多数据时),这一事实是我们将反复回顾的主题。 + +# 值管理 + +如果我们对上述代码片段进行扩展,并调用 `lookup.get("Paul")`,返回的值将是 `1234`。在处理像 `i32` 这样的原始类型时,很难直观地理解 `get` 方法和它的可选返回类型 `?i32` 或更通用的 `?V`(其中 `V` 表示任何值类型)之间的区别。考虑到这一点,让我们通过替换 `i32` 为一个封装了更多信息的 `User` 类型来展示这一概念: + +```zig +// 示例:如果 i32 被替换为一个 User 类型,则会涉及更复杂的数据结构和访问逻辑。 +``` + +在上述场景中,我们引入了一个新的类型 `User`,用于演示 `get` 方法返回可选值的概念。通过这种方式,我们可以直观地理解 `get` 和 `getPtr` 方法之间的区别,并根据实际需要选择合适的方法来处理不同的数据访问需求。 + +```zig +const std = @import("std"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + var lookup = std.StringHashMap(User).init(allocator); + defer lookup.deinit(); + + try lookup.put("Goku", .{ + .id = 9000, + .name = "Goku", + .super = false, + }); + + var user = lookup.get("Goku").?; + + user.super = true; + std.debug.print("{any}\n", .{lookup.get("Goku").?.super}); +} + +const User = struct { + id: i32, + name: []const u8, + super: bool, +}; +``` + +即使我们设置了 `user.super = true`,在 `lookup` 中的 `User` 的值仍然是 `false`。这是因为在 Zig 中,赋值是通过复制完成的。如果我们保持代码不变,但将 `lookup.get` 改为 `lookup.getPtr`,它将起作用。我们仍然在做赋值,因此仍然在复制一个值,但我们复制的值是哈希表中 `User` 的地址,而不是 `user` 本身。 + +`getPtr` 允许我们获取哈希表中值的引用。如上所示,这具有行为意义;我们可以直接修改存储在哈希表中的值。这也具有性能意义,因为复制大值可能会很昂贵。但是考虑我们上面的可视化,并记住,随着哈希表的填满,值可能会重新定位。考虑到这一点,你能解释为什么这段代码会崩溃吗?: + +```zig +const std = @import("std"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + // change the type, just to make it easier to write this snippet + // the same would happen with our above StringHashMap(User) + var lookup = std.AutoHashMap(usize, usize).init(allocator); + defer lookup.deinit(); + + try lookup.put(100, 100); + const first = lookup.getPtr(100).?; + + for (0..50) |i| { + try lookup.put(i, i); + } + first.* = 200; +} +``` + +如果 `first.* = 200;` 的语法让您感到困惑,那么我们在操作指针,并向其指定的地址写入一个值。这里的指针指向了值数组中某个索引的位置,因此这种语法实际上是在数组内部直接设置了一个值。问题在于,在我们的插入循环过程中,哈希表正在增长,导致底层键和值被重新分配并移动。`getPtr` 函数返回的指针不再有效。在撰写本文时,哈希表默认大小为 8,填充因子是 80%。如果我们在遍历范围 `0..5` 时运行代码一切正常,但当增加一次迭代至 `0..6`(即尝试访问 `array[6]`)时,由于增长操作导致崩溃。在常规使用场景中,此问题通常不构成问题;您不太可能在修改哈希表时持有对某个条目的引用。但是,理解这种情况的发生以及其原因将帮助我们更好地利用其他返回值和键指针的哈希表功能。 + +回到我们的 `User` 示例,如果我们将 `lookup` 的类型从 `std.StringHashMap(User)` 改为 `std.StringHashMap(*User)` 会怎样?最大的影响将是值的生命周期。使用原来的 `std.StringHashMap(User)`,我们可以说 `lookup` 拥有这些值——我们插入的用户嵌入在哈希表的值数组中。这使得生命周期管理变得容易,当我们 `deinit` 我们的 `lookup` 时,底层的键和值数组会被释放。 + +我们的 `User` 有一个 `name: []const u8` 字段。我们的示例使用字符串字面量,它在程序的生命周期中静态存在。然而,如果我们的 `name` 是动态分配的,我们必须显式地释放它。我们将在更详细地探讨指针值时涵盖这一点。 + +使用 `*User` 打破了这种所有权。我们的哈希表存储指针,但它不拥有指针所指向的内容。尽管调用了 `lookup.deinit`,这段代码会导致用户泄漏: + +```zig +const std = @import("std"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + var lookup = std.StringHashMap(*User).init(allocator); + defer lookup.deinit(); + + const goku = try allocator.create(User); + goku.* = .{ + .id = 9000, + .name = "Goku", + .super = false, + }; + try lookup.put("Goku", &goku.address); +} + +const User = struct { + id: i32, + name: []const u8, + super: bool, +}; +``` + +让我们将其可视化: + +``` +lookup + =============================== + ║ keys: values: ║ + ║ -------- ------------- ║ + ║ | Goku* | | 1024d4000 | ----> ------------- + ║ -------- ------------- ║ | 9000 | + ║ | | | | ║ ------------- + ║ -------- ------------- ║ | 1047300e4 |---> ----------------- + =============================== ------------- | G | o | k | u | + | 4 | ----------------- + ------------- + | false | + ------------- +``` + +我们将会在下一节讨论键,现在为了简单起见我们使用“Goku”。 + +双线框是我们的 `lookup`,表示它拥有并负责的内存。我们放入哈希表的引用将指向框外的值。这有许多含义。最重要的是,这意味着值的生命周期与哈希表的生命周期分离,调用 `lookup.deinit` 不会释放它们。 + +有一种常见情况是我们想使用指针并将值的生命周期与哈希表相关联。回想我们崩溃的程序,当对哈希表值的指针变得无效时。正如我所说,这通常不是问题,但在更高级的场景中,你可能希望不同部分的代码引用也存在于哈希表中的值。让我们重新审视上面的可视化,并思考如果我们的哈希表增长并重新定位键和值数组会发生什么: + +```zig +lookup + =============================== + ║ keys: values: ║ + ║ -------- ------------- ║ + ║ | | | | ║ + ║ -------- ------------- ║ + ║ -------- ------------- ║ + ║ | | | | ║ + ║ -------- ------------- ║ + ║ -------- ------------- ║ + ║ | Goku* | | 1024d4000 | ----> ------------- + ║ -------- ------------- ║ | 9000 | + ║ | | | | ║ ------------- + ║ -------- ------------- ║ | 1047300e4 |---> ----------------- + =============================== ------------- | G | o | k | u | + | 4 | ----------------- + ------------- + | false | + ------------- +``` + +这两个数组已经增长、重新分配,并且我们的条目索引已重新计算,但我们实际的 `User`(也就是 `Goku`)仍然驻留在堆中的同一位置(内存位置 1047300e4)。就像 `deinit` 不会改变双线框外的任何内容一样,其他变化(如增长)也不会改变它们。 + +一般来说,你是否应该存储值或指向值的指针将是显而易见的。这主要是因为像 `getPtr` 这样的方法使我们能够直接从哈希表中高效地检索和修改值。无论哪种方式,我们都可以获得性能上的好处,所以性能不是主要考虑因素。重要的是值是否需要比哈希表存活更久和/或在哈希表发生变化时对值的引用是否需要存在(并因此保持有效)。 + +在哈希表和引用的值应该链接的情况下,我们需要在调用 `lookup.deinit` 之前遍历这些值并清理它们: + +```zig +defer { + var it = lookup.valueIterator(); + while (it.next()) |value_ptr| { + allocator.destroy(value_ptr.*); + } + lookup.deinit(); +} +``` + +如果解引用 (`value_ptr.*`) 看起来不对劲,请回到可视化。我们的 `valueIterator` 给我们数组中值的指针,而数组中的值是 `*User`。因此,`value_ptr` 是 `**User`。 + +无论我们是存储 `User` 还是 `*User`,值中任何已分配的字段始终是我们的责任。在一个真实的应用程序中,你的用户名称不会是字符串字面量,它们会是动态分配的。在这种情况下,我们上面的 while 循环需要改为: + +```zig +while (it.next()) |value_ptr| { + const user = value_ptr.*; + allocator.free(user.name); + allocator.destroy(user); +} +``` + +即使我们的值是 `User`,其字段也是我们的责任(认为 `lookup.deinit` 会知道如何/需要释放什么有点荒谬): + +```zig +while (it.next()) |value_ptr| { + allocator.free(value_ptr.name); +} +``` + +在最后一种情况下,由于我们存储的是 `User` 而不是 `*User`,我们的 `value_ptr` 是指向 `User` 的指针(不像之前那样是指向指针的指针)。 + +# Keys + +我们可以开始和结束这一节:我们关于值的所有内容同样适用于键。这是100%正确的,但这在某种程度上不太直观。大多数开发人员很快就能理解,存储在哈希表中的堆分配的 `User` 实例有其自身的生命周期,需要显式管理/释放。但由于某些原因,这对于键来说并不那么明显。 + +像值一样,如果我们的键是原始类型(例如整数),我们不必做任何特别的事情。原始类型的键直接存储在哈希表的键数组中,因此其生命周期和内存与哈希表绑定。这是一种非常常见的情况。但另一种常见情况是使用 `std.StringHashMap` 的字符串键。这常常让刚接触 Zig 的开发人员感到困惑,但你需要保证字符串键在哈希表使用它们期间始终有效。而且,如果这些键是动态分配的,你需要确保在不再使用时释放它们。这意味着对键进行与值相同的处理。 + +让我们再次可视化我们的哈希表,但这次正确表示一个字符串键: + +``` +lookup + =================================== + ║ keys: values: ║ + ║ ------------- ------------ ║ + ║ | 1047300e4 | | 1024d4000 | ----> ------------- + ║ ------------- ------------- ║ | 9000 | + ║ | | | | ║ ------------- + ║ ------------- ------------- ║ | 1047300e4 |---> ----------------- + =================================== ------------- | G | o | k | u | + | 4 | ----------------- + ------------- + | false | + ------------- +``` + +在这个例子中,我们的键实际上是 `user.name`。将键作为值的一部分是非常常见的。这里是它可能的样子: + +```zig +const user = try allocator.create(User); +user.* = .{ + .id = 9000, + .super = false, + // 模拟来自动态源(如数据库)的名称 + .name = try allocator.dupe(u8, "Goku"), +}; +try lookup.put(user.name, user); +``` + +在这种情况下,我们之前的清理代码是足够的,因为我们已经在释放作为我们键的 `user.name`: + +```zig +defer { + var it = lookup.iterator(); + while (it.next()) |value_ptr| { + const user = value_ptr.*; + allocator.free(user.name); + allocator.destroy(user); + } + lookup.deinit(); +} +``` + +但在键不是值的一部分的情况下,我们需要迭代并释放这些键。在许多情况下,你需要同时迭代键和值并释放它们。我们可以通过释放键引用的名称而不是用户来模拟这一点: + +```zig +defer { + var it = lookup.iterator(); + while (it.next()) |kv| { + // 这个.. + allocator.free(kv.key_ptr.*); + + // 和下面的是一样的,但仅仅因为 user.name 是我们的键 + // allocator.free(user.name); + + allocator.destroy(kv.value_ptr.*); + } + lookup.deinit(); +} +``` + +我们使用 `iterator()` 而不是 `iteratorValue()` 来访问 `key_ptr` 和 `value_ptr`。 + +最后要考虑的是如何从我们的 `lookup` 中移除值。尽管使用了改进的清理逻辑,这段代码仍会导致键和堆分配的 `User` 泄漏: + +```zig +var lookup = std.StringHashMap(*User).init(allocator); + +defer { + var it = lookup.iterator(); + while (it.next()) |kv| { + allocator.free(kv.key_ptr.*); + allocator.destroy(kv.value_ptr.*); + } + lookup.deinit(); +} + +const user = try allocator.create(User); +user.* = .{ + .id = 9000, + .super = false, + // 模拟来自动态源(如数据库)的名称 + .name = try allocator.dupe(u8, "Goku"), +}; +try lookup.put(user.name, user); + +// 我们加上了这行! +_ = lookup.remove(user.name); +``` + +最后一行从我们的哈希表中移除了条目,所以我们的清理例程不再迭代它,也不会释放名称或用户。我们需要使用 `fetchRemove` 而不是 `remove` 来获取被移除的键和值: + +```zig +if (lookup.fetchRemove(user.name)) |kv| { + allocator.free(kv.key); + allocator.destroy(kv.value); +} +``` + +`fetchRemove` 不返回键和值的指针,而是返回实际的键和值。这并不会改变我们的使用方式,但显然为什么返回键和值而不是指针是很明显的,因为从哈希表中移除的条目,不再有指向哈希表中键和值的有效指针——它们已经被移除了。 + +所有这些都假设你的值和键在从哈希表中移除时需要被释放/失效。有些情况下,你的值(更少见的是键)的生命周期与它们在哈希表中的存在完全无关。在这些情况下,你需要在适合你的应用程序的情况下释放内存。没有通用的模式或指导适用。 + +对于大多数情况,在处理非原始键或值时,关键是当你调用哈希表的 `deinit` 时,你为键和值分配的任何内存不会被自动释放;你需要自己处理。 + +# getOrPut + +虽然我们已经讨论过的内容有很多含义,但对我来说,直接暴露键和值指针的最大好处之一是 `getOrPut` 方法。 + +如果我让你在 Go 或大多数语言中存储带名称的计数器,你会写出类似这样的代码: + +```go +count, exists := counters[name] +if exists == false { + counters[name] = 1 +} else { + counters[name] = count + 1; +} +``` + +这段代码需要两次查找。尽管我们被训练成不考虑哈希表访问通常为 O(1),实际情况是操作次数越少运行速度越快;而计算哈希码并非最经济的操作(其性能取决于键的类型和长度),碰撞还会增加额外开销。「getOrPut」方法通过返回一个值指针和一个指示是否找到该值的布尔值来解决这个问题。 + +换句话说,使用 `getOrPut` 我们要么获得一个指向找到的值的指针,要么获得一个指向应放置项位置的指针。我们还得到一个布尔值,用于指示是哪种情况。这使得上述插入或更新操作仅需一次查找: + +```zig +const gop = try counters.getOrPut(name); +if (gop.found_existing) { + gop.value_ptr.* += 1; +} else { + gop.value_ptr.* = 1; +} +``` + +当然,只要不对哈希表进行修改,`value_ptr` 就应被视为有效。顺便提一句,这同样适用于我们通过 `iterator()`、`valueIterator` 和 `keyIterator` 获取的迭代键和值,原因相同。 + +# 结论 + +希望你现在对使用「std.HashMap」、「std.AutoHashMap」和「std.StringHashMap」以及它们的「unmanaged」变体感到更加得心应手。虽然你可能永远不需要提供自己的上下文(例如「hash」和「eql」函数),但了解这是一个选项是有益的。在日常编程中,可视化数据尤其有用,尤其是在使用指针和添加间接层次时。每当我处理 `value_ptr` 或 `key_ptr` 时,我都会想到这些切片以及值或键与这些切片中值或键的实际地址之间的区别。 + +> 原文地址: https://www.openmymind.net/Zigs-HashMap-Part-2/ diff --git a/content/post/2024-06-16-leveraging-zig-allocator.smd b/content/post/2024-06-16-leveraging-zig-allocator.smd new file mode 100644 index 0000000..91c11d8 --- /dev/null +++ b/content/post/2024-06-16-leveraging-zig-allocator.smd @@ -0,0 +1,163 @@ +--- +.title = "Zig 分配器的应用", +.date = @date("2024-06-16T12:11:44+0800"), +.author = "ZigCC", +.layout = "post.shtml", +.draft = false, +--- + +> 原文地址: + +假设我们想为Zig编写一个 [HTTP服务器库](https://github.com/karlseguin/http.zig)。这个库的核心可能是线程池,用于处理请求。以简化的方式来看,它可能类似于: + +```zig +fn run(worker: *Worker) void { + while (queue.pop()) |conn| { + const action = worker.route(conn.req.url); + action(conn.req, conn.res) catch { // TODO: 500 }; + worker.write(conn.res); + } +} +``` + +作为这个库的用户,您可能会编写一些动态内容的操作。如果假设在启动时为服务器提供分配器(Allocator),则可以将此分配器传递给动作: + +```zig +fn run(worker: *Worker) void { + const allocator = worker.server.allocator; + while (queue.pop()) |conn| { + const action = worker.route(conn.req.url); + action(allocator, conn.req, conn.res) catch { // TODO: 500 }; + worker.write(conn.res); + } +} +``` + +这允许用户编写如下的操作: + +```zig +fn greet(allocator: Allocator, req: *http.Request, res: *http.Response) !void { + const name = req.query("name") orelse "guest"; + res.status = 200; + res.body = try std.fmt.allocPrint(allocator, "Hello {s}", .{name}); +} +``` + +虽然这是一个正确的方向,但存在明显的问题:分配的问候语从未被释放。我们的`run`函数不能在写回应后就调用`allocator.free(conn.res.body)`,因为在某些情况下,主体可能不需要被释放。我们可以通过使动作必须 `write()` 回应并因此能够`free`它所做的任何分配来结构化API,但这将使得添加一些功能变得不可能,比如支持中间件。 + +最佳和最简单的方法是使用 `ArenaAllocator` 。其工作原理很简单:当我们`deinit`时,所有分配都被释放。 + +```zig +fn run(worker: *Worker) void { + const allocator = worker.server.allocator; + while (queue.pop()) |conn| { + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + const action = worker.route(conn.req.url); + action(arena.allocator(), conn.req, conn.res) catch { // TODO: 500 }; + worker.write(conn.res); + } +} +``` + +`std.mem.Allocator` 是一个 "[接口](https://www.openmymind.net/Zig-Interfaces/)" ,我们的动作无需更改。 `ArenaAllocator` 对HTTP服务器来说是一个很好的选择,因为它们与请求绑定,具有明确/可理解的生命周期,并且相对短暂。虽然有可能滥用它们,但可以说:使用更多! + +我们可以更进一步并重用相同的Arena。这可能看起来不太有用,但是请看: + +```zig +fn run(worker: *Worker) void { + const allocator = worker.server.allocator; + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + while (queue.pop()) |conn| { + // 魔法在此处! + defer _ = arena.reset(.{.retain_with_limit = 8192}); + const action = worker.route(conn.req.url); + action(arena.allocator(), conn.req, conn.res) catch { // TODO: 500 }; + worker.write(conn.res); + } +} +``` + +我们将Arena移出了循环,但重要的部分在内部:每个请求后,我们重置了Arena并保留最多8K内存。这意味着对于许多请求,我们无需访问底层分配器(`worker.server.allocator`)。这种方法简化了内存管理。 + +现在想象一下,如果我们不能用 `retain_with_limit` 重置 Arena,我们还能进行同样的优化吗?可以,我们可以创建自己的分配器,首先尝试使用固定缓冲区分配器(FixedBufferAllocator),如果分配适配,回退到 Arena 分配器。 + +这里是 `FallbackAllocator` 的完整示例: + +```zig +const FallbackAllocator = struct { + primary: Allocator, + fallback: Allocator, + fba: *std.heap.FixedBufferAllocator, + + pub fn allocator(self: *FallbackAllocator) Allocator { + return .{ + .ptr = self, + .vtable = &.{.alloc = alloc, .resize = resize, .free = free}, + }; + } + + fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ra: usize) ?[*]u8 { + const self: *FallbackAllocator = @ptrCast(@alignCast(ctx)); + return self.primary.rawAlloc(len, ptr_align, ra) + orelse self.fallback.rawAlloc(len, ptr_align, ra); + } + + fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ra: usize) bool { + const self: *FallbackAllocator = @ptrCast(@alignCast(ctx)); + if (self.fba.ownsPtr(buf.ptr)) { + if (self.primary.rawResize(buf, buf_align, new_len, ra)) { + return true; + } + } + return self.fallback.rawResize(buf, buf_align, new_len, ra); + } + + fn free(_: *anyopaque, _: []u8, _: u8, _: usize) void { + // we noop this since, in our specific case, we know + // the fallback is an arena, which won't free individual items + } +}; +``` + +我们的`alloc`实现首先尝试使用我们定义的"主"分配器进行分配。如果失败,我们会使用"备用"分配器。作为`std.mem.Allocator`接口的一部分,我们需要实现的`resize`方法会确定正在尝试扩展内存的所有者,并然后调用其`rawResize`方法。为了保持代码简单,我在这里省略了`free`方法的具体实现——在这种特定情况下是可以接受的,因为我们计划使用"主"分配器作为`FixedBufferAllocator`,而"备用"分配器则会是`ArenaAllocator`(因此所有释放操作会在arena的`deinit`或`reset`时进行)。 + +接下来我们需要改变我们的`run`方法以利用这个新的分配器: + +```zig +fn run(worker: *Worker) void { + const allocator = worker.server.allocator; // 这是FixedBufferAllocator底层的内存 + const buf = try allocator.alloc(u8, 8192); // 分配8K字节的内存用于存储数据 + defer allocator.free(buf); // 完成后释放内存 + + var fba = std.heap.FixedBufferAllocator.init(buf); // 初始化FixedBufferAllocator + + while (queue.pop()) |conn| { + defer fba.reset(); // 重置FixedBufferAllocator,准备处理下一个请求 + + var arena = std.heap.ArenaAllocator.init(allocator); // 初始化ArenaAllocator用于分配额外内存 + defer arena.deinit(); + + var fallback = FallbackAllocator{ + .fba = &fba, + .primary = fba.allocator(), + .fallback = arena.allocator(), + }; // 创建FallbackAllocator,包含FixedBufferAllocator和ArenaAllocator + + const action = worker.route(conn.req.url); // 路由请求到对应的动作处理函数 + action(fallback.allocator(), conn.req, conn.res) catch { // 处理动作执行中的错误 }; + + worker.write(conn.res); // 写回响应信息给客户端 + } +} +``` + +这种方法实现了类似于在`retain_with_limit`中重置arena的功能。我们创建了一个可以重复使用的`FixedBufferAllocator`,用于处理每个请求的8K字节内存需求。由于一个动作可能需要更多的内存,我们仍然需要`ArenaAllocator`来提供额外的空间。通过将`FixedBufferAllocator`和`ArenaAllocator`包裹在我们的`FallbackAllocator`中,我们可以确保任何分配都首先尝试使用(非常快的)`FixedBufferAllocator`,当其空间用尽时,则会切换到`ArenaAllocator`。 + +我们通过暴露`std.mem.Allocator`接口,可以调整如何工作而不破坏`greet`。这不仅简化了资源管理(例如通过`ArenaAllocator`),而且通过重复使用分配来提高了性能(类似于我们做的`retain_with_limit`或`FixedBufferAllocator`的操作)。 + +这个示例应该能突出显示我认为明确的分配器提供的两个实际优势: + +1. 简化资源管理(通过类似`ArenaAllocator`的方式) +2. 通过重用分配来提高性能(例如我们之前在 `retain_with_limit` 或 `FixedBufferAllocator` 时所做的一样) diff --git a/content/post/2024-08-12-zoop.smd b/content/post/2024-08-12-zoop.smd new file mode 100644 index 0000000..61b18b3 --- /dev/null +++ b/content/post/2024-08-12-zoop.smd @@ -0,0 +1,486 @@ +--- +.title = "zoop 实现原理分析", +.date = @date("2024-08-12T14:31:22+08:00"), +.author = "朱亚东", +.layout = "post.shtml", +.draft = false, +--- + +# zoop 是什么 + +zoop 是 zig 的一个 OOP 解决方案,详细信息可以看看 [zoop官网](https://zhuyadong.github.io/zoop-docs/)。 + +# 为什么不用别的 OOP 语言 + +简单的说,是我个人原因,必需使用 zig 的同时,还一定要用 OOP,所以有了 zoop。 + +# zoop 入门 + +## 类和方法 + +```zig +pub const Base = struct { + pub usingnamespace zoop.Fn(@This()); + mixin: zoop.Mixin(@This()), +} +``` + +2-3行是一个struct成为zoop类必需的两行,这样一来,`Base` 就成为了一个 zoop 的类。 + +创建 `Base` 的对象有两种方法: + +- 在堆上: `var obj = try Base.new(allocator);` +- 在栈上:`var obj = Base.make(); obj.initMixin();` + +栈上创建的对象必需调用对象的 `initMixin()` 方法,因为对象地址对 zoop 来说很重要,而在 `make()` 中是无法知道这个返回的对象会放在什么地址,只能通过外部调用 `initMixin()` 来通知 zoop 这个地址。 + +销毁对象的方法是 `obj.destroy()`,这是 zoop 自动为所有类加上的方法,不需要用户去定义。 + +我们可以给 `Base` 加上方法和字段,就是正常的 zig 方法和字段: + +```zig +pub const Base = struct { + pub usingnamespace zoop.Fn(@This()); + mixin: zoop.Mixin(@This()), + + name: []const u8, + + pub fn init(self: *Base, name: []const u8) void { + self.name = name; + } +} +``` + +因为创建 zoop 类对象的方法不是 `init()`,因此在 zoop 类中,一般把 `init()` 当作初始化方法而不是创建方法。这种常规的方法,是没法被子类继承的,属于类的私有方法。要定义可以继承的方法,需要用如下形式来定义: + +```zig +pub const Base = struct { + ...// 和上面一样,这里不写了 + + pub fn Fn(comptime T: type) type { + return zoop.Method(.{ + struct { + pub fn getName(this: *T ) []const u8 { + return this.cast(Base).name; + } + }, + }); + } +} +``` + +看起来有点怪,下面解释 zoop 实现原理的时候会解释这些奇怪的地方,现在我们先熟悉用法,不要在意这些细节^\_^。 + +上面的代码给 `Base` 添加了一个可以继承的方法 `getName()`。 + +## 类的继承 + +zoop 引入一个关键字 `extends` 用来实现继承,比如下面我们定义 `Base` 的子类 `Child`: + +```zig +pub const Child = struct { + // 表示我继承 Base + pub const extends = .{Base}; + + // 必要的两行 + pub usingnamespace zoop.Fn(@This()); + mixin: zoop.Mixin(@This()); + + // 我的初始化函数,里面调用父类的初始化函数 + pub fn init(self: *Child, name: []const u8) void { + self.cast(Base).init(name); + } +} +test { + const t = std.testing; + + var sub = try Child.new(t.allocator); + sub.init("sub"); + defer sub.destroy(); + const name = sub.getName(); // 使用继承来的方法 getName() + try t.expectEqualStrings(name, "sub"); +} +``` + +## 接口定义 + +zoop 中的接口,实际上是一个胖指针。下面我们定义一个接口 `IGetName`: + +```zig +pub const IGetName = struct { + // 定义接口的 `Vtable`,说明接口有哪些方法 + pub const Vtable = zoop.DevVtable(@This(), struct { + getName: *const fn(self: *anyopaque) []const u8, + }); + // 必需的一行 + pub usingnamespace zoop.Api(@This()); + + // 必需的两个字段 + ptr: *anyopaque, + vptr: *const Vtable, + + // 必需的胶水代码,用来调用 `Vtable` 里面的函数 + pub fn Api(comptime I:type) type { + return struct { + pub fn getName(self: I) []const u8 { + return self.vptr.getName(self.ptr); + } + } + } +} +``` + +上面的代码具体原理下面会说到,这里大家知道接口就是这样定义的就行了。上面的代码定义了接口 `IGetName`,这个接口有一个方法 `getName()`。 + +## 接口实现 + +上面的 `Base` 类正好也有个符合 `IGetName` 接口的方法 `getName()`,那我们修改一下 `Base` 的代码让它来实现 `IGetName` 接口: + +```zig +pub const Base = struct { + // 我实现 IGetName 接口 + pub const extends = .{IGetName}; + + // 以下省略 + ... +} +``` + +可以看到实现接口和继承用的同样一个关键字 `extends`。因为子类会继承父类的接口,所以这样一来,`Child` 也自动实现了 `IGetName` 接口。 + +## 方法重写和虚函数调用 + +我们修改上面 `Child` 的代码,重写 `getName()` 方法: + +```zig +pub const Child = struct { + ... + // 省略上面已知代码,下面代码重写了 Base.getName() + + pub fn Fn(comptime T: type) type { + return zoop.Method(.{ + struct { + pub fn getName(_: *T) []const u8 { + return "override"; + } + }, + }); + } +} +``` + +要注意,只有可继承方法才可以被重写,可继承方法和重写的方法都要通过上面 `pub fn Fn(comptime T: type) type` 这样的方式来定义。 +重写的方法,只有通过接口,才能进行虚函数调用,下面是例子: + +```zig +const t = std.testing; + +var child = try Child.new(t.allocator); +child.init("Child"); +defer child.destroy(); + +var base: *Base = child.cast(Base); + +// 不通过接口调用 getName() +try t.expectEqualStrings(child.getName(), "override"); +try t.expectEqualStrings(base.getName(), "Child"); + +// 通过接口调用(虚函数调用) getName(); +try t.expectEqualStrings(child.as(IGetName).?.getName(), "override"); +try t.expectEqualStrings(base.as(IGetName).?.getName(), "override"); +``` + +上面例子中 `cast`、`as` 属于 zoop 中的类型转换,详细可以参考 [zoop 类型转换](https://zhuyadong.github.io/zoop-docs/guide/as-cast) + +那么 zoop 的基本使用方法就介绍到这里,下面我们开始介绍 zoop 的实现原理。 + +# 预设场景 + +接下来的讨论基于如下的属于 `mymod` 模块的类和接口: + +```zig +/// 接口 IGetName +pub const IGetName = struct { + pub const Vtable = zoop.DevVtable(@This(), struct { + getName: *const fn(self: *anyopaque) []const u8, + }); + pub usingnamespace zoop.Api(@This()); + + ptr: *anyopaque, + vptr: *const Vtable, + + pub fn Api(comptime I:type) type { + return struct { + pub fn getName(self: I) []const u8 { + return self.vptr.getName(self.ptr); + } + } + } +} + +/// 接口 ISetName +pub const ISetName = struct { + pub const Vtable = zoop.DevVtable(@This(), struct { + setName: *const fn(self: *anyopaque, name: []const u8) void, + }); + pub usingnamespace zoop.Api(@This()); + + ptr: *anyopaque, + vptr: *const Vtable, + + pub fn Api(comptime I:type) type { + return struct { + pub fn setName(self: I, name: []const u8) void { + self.vptr.setName(self.ptr, name); + } + } + } +} + +/// 基类 Base +pub const Base = struct { + pub const extends = .{ISetName}; + pub usingnamespace zoop.Fn(@This()); + + name: []const u8, + mixin: zoop.Mixin(@This()), + + pub fn Fn(comptime T: type) type { + return zoop.Method(.{ + struct { + pub fn setName(this: *T, name: []const u8) void { + this.cast(Base).name = name; + } + }, + }); + } +} + +/// 子类 Child +pub const Child = struct { + pub const extends = .{Base, IGetName}; + pub usingnamespace zoop.Fn(@This()); + + mixin: zoop.Mixin(@This()), + + pub fn Fn(comptime T: type) type { + return zoop.Method(.{ + struct { + pub fn getName(this: *T) []const u8 { + return this.cast(Base).name; + } + }, + }); + } +} +``` + +接口有两个: + +- `IGetName`: 接口方法 `getName` +- `ISetName`: 接口方法 `setName` + +类有两个: + +- `Base`: 基类,实现接口 `ISetName` +- `Child`: 子类,继承 `Base`,并实现接口 `IGetName` + +# 核心数据结构 `zoop.Mixin(T)` + +我们看看两个类的 `mixin` 这个数据里面有什么: + +```zig +pub const VtableFunc = *const fn (ifacename: []const u8) ?*IObject.Vtable; +pub const SuperPtrFunc = *const fn (rootptr: *anyopaque, typename: []const u8) ?*anyopaque; + +zoop.Mixin(Base) = struct { + deallocator: ?std.mem.Allocator = null, + meta: struct { + rootptr: ?*anyopaque = null, + typeinfo: *struct { + typename: []const u8, + getVtable: VtableFunc, + getSuperPtr: SuperPtrFunc, + }, + }, + data: struct {}, +} + +zoop.Mixin(Child) = struct { + deallocator: ?std.mem.Allocator = null, + meta: struct { + rootptr: ?*anyopaque = null, + typeinfo: *struct { + typename: []const u8, + getVtable: VtableFunc, + getSuperPtr: SuperPtrFunc, + }, + }, + data: struct { + mymod_Base: Base, + }, +} +``` + +可以看出,两者的唯一的差别在于 `Child.mixin.data` 里面包含了一个 `Base`, 而 `Base.mixin.data` 里面是空的。说明在 zoop 中,类有多少个父类,则类的 `mixin.data` 中,就有多少个父类的数据。 + +我们再来看看 `mixin.meta` 这个数据。先看看 `rootptr` 这个字段,如果我们现在有一个 `Base` 对象 `base`,那么 `base.mixin.meta.rootptr == &base` 是成立的;如果现在有一个 `Child` 对象 `child`,那么如下两条成立: + +- `child.mixin.meta.rootptr == &child` +- `child.mixin.data.mymod_Base.mixin.meta.rootptr == &child` + +事实上,`child.mixin.data.mymod_Base.mixin.meta` 里面的内容就是完全复制的 `child.mixin.meta`,因为所有内层对象的 `mixin.meta` 都是复制的最外层那个对象的 `mixin.meta`,因而所有对象的 `rootptr` 都指向最外层对象,这也是为什么叫 `rootptr` 的原因。 + +再看看 `typeinfo` 字段,这个字段是一个有3个字段的结构: + +- `typename`: 这是 `rootptr` 指向对象的类型名 +- `getVtable`: 根据接口名获得接口 `Vtable` 的函数 +- `getSuperPtr`: 根据父类名获得 `mixin.data` 中父类指针 + +上面两个函数获取的都是最外层对象的数据。根据对 `mixin` 数据的分析,zoop 的类型转换的原理就很清楚了,大家可以参考官网上关于 [类型转换](https://zhuyadong.github.io/zoop-docs/guide/as-cast) 的内容。 + +# 动态构造类的方法、接口方法、和 `Vtable` + +OOP 概念中的继承,重写,虚函数,实质其实就是在编译时动态构造需要的方法和属性。zoop 中主要是通过通过 [zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple) 这个模块来进行编译时动态构造。 + +这部分需要大家有一定的 [zig comptime](https://ziglang.org/documentation/0.13.0/#comptime) 的知识,同时如果大家理解了这部分知识,那么 zoop 动态构造方法属性的部分实际不难理解。(建议同时也看看 [zig 圣经](https://course.ziglang.cc/advanced/comptime) 中和 `comptime` 有关的部分,写的很好) + +下面我介绍一下 zoop 中用到的 `comptime` 一些技巧,相信会对大家今后使用 zig 有帮助。 + +## `struct` 很万能 + +`comptime` 编程中,`struct` 是你最好的朋友,想在不同的 `comptime` 函数之间传递数据,最方便的方式,就是通过构造一个 `struct`,把想传递的数据通过 `pub const xxx = ...` 的方式传递出去,通过 `struct` 保存数据最好的地方,就在于这个数据在运行时也是可用的 (`struct` 中的常量,是保存在 exe 的 `.data` 区,运行时可见),[zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple) 就是通过这个方法实现的。 + +## 动态构造 `struct` 的字段,用 `@Type()` + +网上好像很少有关于 `@Type()` 的使用说明,一般都是通过看 `zig.std` 的代码来学习,那我这里就稍微说明一下,希望能对大家有帮助。 +目前 zig 通过 `@Type()`,能动态构造的 `struct`,只有纯字段类型的 `struct` (个人理解)。构造的方法,就是先把计算好的一个 `std.builtin.Type.StructField` 数组传递给 `@Type()` 来返回一个 `struct`,比如以下代码: + +```zig +fn GenStruct() type { + comptime var fields:[2]std.builtin.Type.StructField = undefined; + fields[0] = .{ + .name = "age", + .type = i32, + .default_value = null, + .is_comptime = false, + .alignment = @alignOf(i32), + }; + fields[1] = .{ + .name = "name", + .type = []const u8, + .default_value = null, + .is_comptime = false, + .alignment = @alignOf([]const u8), + }; + + return @Type(.{ + .Struct = .{ + .layout = .auto, + .fields = fields[0..], + .decls = &.{}, + .is_tuple = false, + } + }); +} + +const MyStruct = GenStruct(); +``` + +这样上面的 `MyStruct` 就相当于: + +```zig +const MyStruct = struct { + age: i32, + name: []const u8, +}; +``` + +zoop 动态构造 `Vtable` 就是通过这个方法做到的,参考 [zoop.DefVtable 原理](https://zhuyadong.github.io/zoop-docs/reference/principle#DefVtable) 和 [zoop 源代码](https://github.com/zhuyadong/zoop.git) + +## 动态构造 `struct` 的函数,用 `usingnamespace` + +要想定义 `struct` 中的函数,理论上代码是一定要写在 `struct` 中的,目前 zig 唯一留下的一个口子,就是 `usingnamespace`,zoop 正是利用这个特性,来动态构造 `struct` 的函数。 + +我们回顾一下 `Base` 中定义 `setName` 方法的代码: + +```zig +pub fn Fn(comptime T: type) type { + return zoop.Method(.{ + struct { + pub fn setName(this: *T, name: []const u8) void { + this.cast(Base).name = name; + } + }, + }); +} +``` + +这里 `zoop.Method()` 返回的是什么呢,返回的是: + +```zig +struct { + pub const value = .{ + struct { + pub fn setName(this: *T, name: []const u8) void { + this.cast(Base).name = name; + } + }, + }; +} +``` + +通过返回一个 `struct` 的方式,在它的 `value` 常量中保存了一个 `tuple`,`tuple` 有一个带有方法 `setName` 的 `struct` 元素。众所周知,`tuple` 是可以各种组合的 (参考 [zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple)),于是 zoop 通过 [zoop.Fn](https://zhuyadong.github.io/zoop-docs/reference/zoop#Fn),比如上例中 `Child` 中的 `pub usingnamespace zoop.Fn(@This())`,把 `Child` 类型代入 `Base.Fn` 中,就相当于在 `Child` 内写了如下代码: + +```zig +pub usingnamespace struct { + pub fn setName(this: *Child, name: []const u8) void { + this.cast(Base).name = name; + } + }; +``` + +因而实现了对 `Base.setName()` 方法的继承。 + +## 运行时根据类型找 `Vtable` 和父类指针 + +这个功能的实现当时第一版是使用的 `std.StaticStringMap` 保存了一个类中所有接口名到接口 `Vtable` ,以及父类名到父类数据在本类中的地址偏移的映射。和 C++ 的 `dynamic_cast` 比起来,性能是比较差的。后来看到西瓜大大发的一个链接 [点这里](https://github.com/SuperAuguste/cursed-zig-errors),忽然意识到这不就是我一直想要的 `comptime` 全局变量么,我终于能写出 `typeId(comptime T: type) u32` 这样的函数了: + +```zig +fn typeId(comptime T: type) u32 { + return @intCast(@intFromError(@field(anyerror, "#" ++ @typeName(T)))); +} +``` + +这里利用的,就是上面说到的链接中提示到的一个 zig 的 `error` 类型的特点:访问 `error` 中不存在的值,zig 会生成一个唯一的新值返回,并且这个在 `comptime` 的时候同样有效。 + +有了 `typeId()` 函数,上面的事从根据类型名查哈希表,就变成了在数组中找同样的 `typeid` 了,整数比较对比字符串比较,那性能是快了好几倍的,根据我的了解,C++ 的 `dynamic_cast` 也是在数组中比较 `typeid`,这样一来,zoop 的动态转换性能,就和 C++ 差不多了。 + +利用这个新的 `typeId()` 函数,zoop 怎么做动态类型转换,我从 zoop 中抄一段代码大家一看就明白: + +```zig +/// 返回一个函数,函数的功能是输入接口 `typeid`,返回针对T的该接口的 Vtable +fn getVtableFunc(comptime T: type) VtableFunc { + // 先找出 T 实现的所有接口 + const ifaces = tuple.Append(.{IObject}, Interfaces(T)).value; + + // 所有接口的 typeid 和 Vtable 编译时计算好并保存在 kvs 数组中 + const KV = struct { typeid: u32, vtable: *IObject.Vtable }; + comptime var kvs: [ifaces.len]KV = undefined; + inline for (ifaces, 0..) |iface, i| { + kvs[i] = .{ .typeid = typeId(iface), .vtable = @ptrCast(makeVtable(T, RealVtable(iface))) }; + } + + return (struct { + pub fn func(typeid: u32) ?*IObject.Vtable { + // 根据 typeid,从 kvs 数组中找出 Vtable + for (kvs) |kv| { + if (kv.typeid == typeid) return kv.vtable; + } + return null; + } + }).func; +} +``` + +上面是找 `Vtable` 的实现,找父类指针的实现原理是一样的,大家可以去看 zoop 源代码了解细节。 diff --git a/content/post/2024-11-26-typed-fsm.smd b/content/post/2024-11-26-typed-fsm.smd new file mode 100644 index 0000000..10325a1 --- /dev/null +++ b/content/post/2024-11-26-typed-fsm.smd @@ -0,0 +1,556 @@ +--- +.title = "在 zig 中实现类型安全的有限状态机", +.date = @date("2024-11-26T13:32:00+08:00"), +.author = "sdzx", +.layout = "post.shtml", +.draft = false, +--- + +# 1. 简单介绍类型化有限状态机的优势 + +## 1.1 介绍有限状态机 + +有限状态机(FSM,以下简称状态机)是程序中很常见的设计模式。 + +它包含两个主要的概念状态和消息。状态机程序整体上的行为就是不断地产生消息,处理消息。 + +而状态主要是在代码层面帮助人们理解消息的产生和处理。 + +## 1.2 [typed-fsm-zig](https://github.com/sdzx-1/typed-fsm-zig) + +typed-fsm-zig 是一个利用 zig 类型系统加一些编程规范实现的一个库,用于实现类型安全的有限状态机。 + +它具有以下两点优势: + +1. 类型安全,极大方便代码的编写,修改和重构 + 手写状态机在实际代码中有很大的心智负担,对于它的修改和重构更是如噩梦一样。 + + typed-fsm-zig 在类型上跟踪状态机的变化,使消息的定义,产生,处理都和状态相关联,从而让类型系统帮我们检查这个过程中是否存在状态错误。 + + 在编写,修改和重构的时候,任何状态的错误都会产生编译错误,而这些编译错误能帮助我们快速找到问题,解决问题。 + + > PS:推荐在 zls 中打开保存时检查,这样你几乎能得到一个交互式的状态机开发环境。 + +2. 简单高效,无任何代码生成,能方便与现有逻辑整合 + + typed-fsm-zig 是一种编程的思想,掌握这种思想就能方便的使用它。 + + 在实际的使用中没有任何的代码生成,除了一处隐式的约束要求之外,没有任何其它的控制,开发者完全掌握状态机,因此你可以方便的将它和你现有的代码结合起来。 + +# 2. 例子:修改 ATM 状态机的状态 + +这里我将以一个 ATM 状态机(以下简称 ATM)的例子来展示 typed-fsm-zig 和 zig 的类型系统如何帮助我快速修改 ATM 的状态。 + +为了简单性,这里我不展示构建 ATM 这个例子的过程,感兴趣的可以在这里看到[代码](https://github.com/sdzx-1/typed-fsm-zig/blob/master/examples/atm-gui.zig)。 + +## 2.1 介绍 ATM 状态机 + +ATM 代表自动取款机,因此它的代码的逻辑就是模拟自动取款机的一些行为:插入银行卡,输入 pin,检查 pin,取钱,修改 pin。 + +它的状态机整体如下: + +![ATM](/images/typed-fsm/2.1-1.webp) + +图中椭圆形表示状态,箭头表示消息。 +它包含五种状态:exit, ready, cardInserted, session, changePin。 + +同时它也包含一堆的消息,每个消息都包含了系统状态的转化。 +比如消息 InsertCard 代表将 ATM 的状态从 ready 转化到 cardInserted,这代表用户插入卡。 + +消息 Incorrect 代表将 ATM 的状态从 cardInserted 转化到 cardInserted, +这代表了一种循环,表示用户输错了 pin,但是可以再次尝试输入 pin,当然我们要求最多可以尝试三次。 + +整个程序效果如下: + +![ATM](/images/typed-fsm/2.1-2.webp) + +这里注意消息 Update,它代表更新 pin,同时将状态转从 changePin 换到 ready。 + +![ATM](/images/typed-fsm/2.1-4.webp) + +实际的表现就是在 changePin 的界面中我们修改 pin,然后点击 Change 按钮触发 Update 消息,修改 pin,并返回到 ready 界面。 + +![ATM](/images/typed-fsm/2.1-3.webp) + +接下来的文章中我将修改 Update 的行为,并展示在这个过程中类型系统如何帮助我快速调整代码。 + +## 2.2 修改 Update 消息 + +实际的消息 Update 定义代码如下 + +```zig + pub fn changePinMsg(end: AtmSt) type { + return union(enum) { + Update: struct { v: [4]u8, wit: WitFn(end, .ready) = .{} }, + ... + } + } + +``` + +这里的.ready 就代表了处理完 Update 消息后就会进入 ready 状态。 + +我们修改这里,把它变成.cardInserted,这代表了我们要求更新完 pin 之后进入 cardInserted 界面重新输入新的 pin,这看着是个合理的要求。 + +新的状态图如下: + +![ATM](/images/typed-fsm/2.2-1.webp) + +这时如果我重新编译代码,那么类型系统就会产生下面的错误: + +```bash + +➜ typed-fsm-zig git:(doc) ✗ zig build atm-gui +atm-gui +└─ run atm-gui + └─ zig build-exe atm-gui Debug native 1 errors +examples/atm-gui.zig:301:60: error: expected type 'typed-fsm.Witness(atm-gui.AtmSt,.exit,.ready)', found 'typed-fsm.Witness(atm-gui.AtmSt,.exit,.cardInserted)' + @call(.always_tail, readyHandler, .{ val.wit, ist }); + ~~~^~~~ +src/typed-fsm.zig:9:20: note: struct declared here (2 times) + return struct { + ^~~~~~ +examples/atm-gui.zig:254:46: note: parameter type declared here +pub fn readyHandler(comptime w: AtmSt.EWitness(.ready), ist: *InternalState) void { + ~~~~~~~~~~~~~~^~~~~~~~ +referenced by: + cardInsertedHander__anon_6916: examples/atm-gui.zig:271:13 + readyHander__anon_3925: examples/atm-gui.zig:261:13 + 5 reference(s) hidden; use '-freference-trace=7' to see all references + +``` + +它告诉我们在 301 行存在类型不匹配。因为之前的状态是 ready 所以使用 readyHandler。 + +当我们把 Update 的状态修改为 cardInserted 时,它与 readyHandler 类型不匹配,应该将它修改为 cardInsertedHandler。 + +修改之后的代码如下: + +```zig +@call(.always_tail, cardInsertedHandler, .{ val.wit, ist }); +``` + +在这里类型系统精确的告诉了我们需要修改的地方,以及原因。修改完成后程序即能正确运行。 + +## 2.3 移除 changePin 状态 + +这一节中我们尝试移除 changePin 状态,看看类型系统会给我们什么反馈。 +如果移除 changePin,新的状态图如下: + +![ATM](/images/typed-fsm/2.3-1.webp) + +重新编译项目,将获得类型系统的反馈 + +类型系统的反馈首先是: + +```bash +examples/atm-gui.zig:148:36: error: enum 'atm-gui.AtmSt' has no member named 'changePin' + ChangePin: WitFn(end, .changePin), + ~^~~~~~~~~ +``` + +因为 changePin 状态已经被移除,因此消息 ChangePin(它代表了从 session 进入 changePin 状态)也不应该再存在了,我们移除它再重新编译。 + +新的反馈如下: + +```bash +examples/atm-gui.zig:161:64: error: union 'atm-gui.AtmSt.sessionMsg(.exit)' has no member named 'ChangePin' + if (resource.changePin.toButton()) return .ChangePin; + ~^~~~~~~~~ +``` + +我们移除 ChangePin 消息,因此也将它从消息产生的地方移除,继续重新编译。 + +新的反馈如下: + +```bash +examples/atm-gui.zig:296:10: error: no field named 'ChangePin' in enum '@typeInfo(atm-gui.AtmSt.sessionMsg(.exit)).@"union".tag_type.?' + .ChangePin => |wit| { + ~^~~~~~~~~ +``` + +因为消息 ChangePin 已经不在了,也应将它从消息处理的地方移除,继续重新编译。 + +这一次不再有编译错误产生,我们搞定了一个新的程序,它不再包含 changePin 的逻辑。 + +在这个过程中类型系统帮助我们找到问题和原因。这非常酷!!! + +## 2.4 总结 + +以上是一个简单的例子,展示了 typed-fsm-zig 对于提升状态机编程体验的巨大效果。 + +展示类型系统如何帮助我们指示错误的地方,把复杂的状态机修改变成一种愉快的编程经历。 + +还有些没有讲到的优势如下: + +1. 状态的分离,后端 handler 处理业务的状态变化,前端渲染和消息生成不改变状态。 +2. 消息生成受到类型的限制和状态相关,这样避免错误消息的产生。 + +这些优势对于复杂业务有很大的帮助。 + +接下来我将介绍 typed-fsm-zig 的原理和实现。 + + + +# 3. 原理与实现 + +最开始的版本是[typed-fsm](https://github.com/sdzx-1/typed-fsm),由使用 haskell 实现,它实现了完整类型安全的有限状态机。 + +typed-fsm 基于[Mcbride Indexed Monad](https://hackage.haskell.org/package/typed-fsm-0.3.0.1/docs/Data-IFunctor.html): + +```haskell +type a ~> b = forall i. a i -> b i + +class IMonad m where + ireturn :: a ~> m a + ibind :: (a ~> m b) -> (m a ~> m b) +``` + +这是一种特殊的 monad,能在类型上为不确定状态建模。 + +而在 zig 实现中移除了对 Monad 语义的需求,保留了在类型上追踪状态的能力。 + +所以它不具备完整的类型安全的能力,需要依靠编程规范来约束代码的行为。我认为这样的取舍是值得的,它的类型安全性在 zig 中完全够用。 + +以下是一个原型例子,它包含了 typed-fsm-zig 的核心想法。看不懂不需要担心,接下来我将详细解释这些代码。 + +```zig +const std = @import("std"); + +pub fn main() !void { + var val: i32 = 0; + const s1Wit = Witness(Exmaple, .exit, .s1){}; + _ = s1Handler(s1Wit, &val); +} + +pub fn Witness(T: type, end: T, start: T) type { + return struct { + pub fn getMsg(self: @This()) @TypeOf(start.STM(end).getMsg) { + _ = self; + if (end == start) @compileError("Can't getMsg!"); + return start.STM(end).getMsg; + } + + pub fn terminal(_: @This()) void { + if (end != start) @compileError("Can't terminal!"); + return {}; + } + }; +} +const Exmaple = enum { + exit, + s1, + s2, + + // State to Message union + pub fn STM(start: Exmaple, end: Exmaple) type { + return switch (start) { + .exit => exitMsg(end), + .s1 => s1Msg(end), + .s2 => s2Msg(end), + }; + } +}; + +pub fn exitMsg(_: Exmaple) void { + return {}; +} + +pub fn s1Msg(end: Exmaple) type { + return union(enum) { + Exit: Witness(Exmaple, end, .exit), + S1ToS2: Witness(Exmaple, end, .s2), + pub fn getMsg(ref: *const i32) @This() { + if (ref.* > 20) return .Exit; + return .S1ToS2; + } + }; +} +pub fn s2Msg(end: Exmaple) type { + return union(enum) { + S2ToS1: Witness(Exmaple, end, .s1), + pub fn getMsg() @This() { + return .S2ToS1; + } + }; +} + +fn s1Handler(val: Witness(Exmaple, .exit, .s1), ref: *i32) void { + std.debug.print("val: {d}\n", .{ref.*}); + switch (val.getMsg()(ref)) { + .Exit => |wit| wit.terminal(), + .S1ToS2 => |wit| { + ref.* += 1; + s2Handler(wit, ref); + }, + } +} +fn s2Handler(val: Witness(Exmaple, .exit, .s2), ref: *i32) void { + switch (val.getMsg()()) { + .S2ToS1 => |wit| { + ref.* += 2; + s1Handler(wit, ref); + }, + } +} + +``` + +首先是 Witness,它是一个类型上的证据,用来跟踪类型上状态的变化。 + +这里有一些介绍 Witness 思想的[文章 1](https://wiki.haskell.org/Type_witness),[文章 2](https://serokell.io/blog/haskell-type-level-witness)。 + +感兴趣的可以看一下,看懂这些要求你了解 GADT,上面提到的 Mcbirde Indexed Monad 本质就是在 GADT 类型上的 monad。 + +在这里的 Winess 三个参数分别表示: + +1. T 表示状态机的类型, +2. end 表示终止时的状态, +3. start 表示当前的状态。 + +它还有两个函数: + +1. getMsg 表示从外部获取消息的函数 +2. terminal 表示终止状态机的函数。 + +当 end == start 时表示当前处于终止状态,因此 Witness 只能使用 terminal 函数,当 end != start 时表示当前不处于终止状态,应该继续从外部获取消息,因此 Witness 只能使用 getMsg 函数。 + +```zig +pub fn Witness(T: type, end: T, start: T) type { + return struct { + pub fn getMsg(self: @This()) @TypeOf(start.STM(end).getMsg) { + if (end == start) @compileError("Can't getMsg!"); + _ = self; + return start.STM(end).getMsg; + } + + pub fn terminal(_: @This()) void { + if (end != start) @compileError("Can't terminal!"); + return {}; + } + }; +} + +``` + +我们在这里定义状态。Example 包含三个状态:exit,s1,s2。我们将在类型上跟踪这些状态的变化。 + +注意这里的 STM 函数,它代表如何将状态映射到对应的消息集合。在实际 typed-fsm-zig 的代码中,这就是我所说的那一处隐式的约束要求。 + +实际代码中会将消息集合整合在 enum 的内部,使用特殊的命名规范将状态与消息集合对应。目前的隐式规范是在状态后面加上 Msg。 + +```zig +const Exmaple = enum { + exit, + s1, + s2, + + // State to Message union + pub fn STM(start: Exmaple, end: Exmaple) type { + return switch (start) { + .exit => exitMsg(end), + .s1 => s1Msg(end), + .s2 => s2Msg(end), + }; + } +}; + +``` + + + +接下来是消息的定义和产生, + +```zig +// exit 状态下没有任何消息 +pub fn exitMsg(_: Exmaple) void { + return {}; +} + +// s1 状态下有两个消息 Exit 和 S1ToS2, 他们分别将状态转化为 exit 和 s2 +pub fn s1Msg(end: Exmaple) type { + return union(enum) { + Exit: Witness(Exmaple, end, .exit), + S1ToS2: Witness(Exmaple, end, .s2), + + // getMsg 函数表明在 s1 状态下如何产生消息,这里受到类型系统的约束, + // 在 s1 的状态下不会产生其它状态的消息 + pub fn getMsg(ref: *const i32) @This() { + if (ref.* > 20) return .Exit; + return .S1ToS2; + } + }; +} + +// s2 状态下有一个消息 S2ToS1 +pub fn s2Msg(end: Exmaple) type { + return union(enum) { + S2ToS1: Witness(Exmaple, end, .s1), + + pub fn getMsg() @This() { + return .S2ToS1; + } + }; +} + +``` + + + +最后一部分是消息的处理。 + +整体的逻辑是通过 Witness 的 getMsg 函数从外部获取消息,然后通过模式匹配处理消息。 +每个消息又包含接下来状态的 Witness,然后使用对应的函数处理这些 Witness。 + +通过 Witness 让类型系统帮我们检查函数的调用是否正确。 + +通过对消息进行模式匹配,编译器能确定我们是否正确且完整的处理了所有的消息。 + +这些对于代码的编写,修改,重构都有巨大的帮助。 + +```zig +fn s1Handler(val: Witness(Exmaple, .exit, .s1), ref: *i32) void { + std.debug.print("val: {d}\n", .{ref.*}); + switch (val.getMsg()(ref)) { + .Exit => |wit| wit.terminal(), + .S1Tos2 => |wit| { + ref.* += 1; + s2Handler(wit, ref); + }, + } +} +fn s2Handler(val: Witness(Exmaple, .exit, .s2), ref: *i32) void { + switch (val.getMsg()()) { + .S2Tos1 => |wit| { + ref.* += 2; + s1Handler(wit, ref); + }, + } +} + +``` + +以上就是 typed-fsm-zig 核心想法的完整介绍。接下来我将介绍需要的编程规范。 + +# 4. typed-fsm-zig 需要哪些编程规范 + +1. 状态和消息集合之间需要满足的隐式命名规范 + + 以 ATM 为例: + + exit -- exitMsg + + ready -- readyMsg + + cardInserted -- cardInsertedMsg + + session -- sessionMsg + +```zig + +const AtmSt = enum { + exit, + ready, + cardInserted, + session, + + pub fn exitMsg(_: AtmSt) type { + return void; + } + + pub fn readyMsg(end: AtmSt) type { + return union(enum) { + ExitAtm: WitFn(end, .exit), + InsertCard: WitFn(end, .cardInserted), + + pub fn genMsg() @This() { + ... + } + }; + } + + pub fn cardInsertedMsg(end: AtmSt) type { + return union(enum) { + Correct: WitFn(end, .session), + Incorrect: WitFn(end, .cardInserted), + EjectCard: WitFn(end, .ready), + + pub fn genMsg(ist: *const InternalState) @This() { + ... + } + }; + } + + pub fn sessionMsg(end: AtmSt) type { + return union(enum) { + Disponse: struct { v: usize, wit: WitFn(end, .session) = .{} }, + EjectCard: WitFn(end, .ready), + + pub fn genMsg(ist: *const InternalState) @This() { + ... + } + }; + } +}; +``` + +2. 除了 exit 状态外,其它消息需要包含 genMsg 函数用于产生消息,任何消息都必须带有 Witness + +3. 状态机都需要定义退出状态,尽管你可能永远也不会退出状态机,但退出状态作用于类型上,是不可缺少的 + +4. 在互相调用其它 handler 的时候使用尾递归的语法,并且在必须在语句块最后处理消息附带的 Witness + +由于 zig 的实现缺少 Mcbride Indexed Monad 语义的支持,因此类型系统不能阻止你进行下面的操作: + +```zig + +// 使用上面 Example 的处理函数 s1Handler, 将它修改成下面的样子。 +// 这里的 s1Handler 不应该被多次调用,在 haskell 版本的 typed-fsm 中,类型系统能检查出这里类型错误,但是在 zig 实现中无法做到, +// 因此我们要求只能在语句块最后有一个处理 Witness 的语句 +fn s2Handler(val: Witness(Exmaple, .exit, .s2), ref: *i32) void { + switch (val.getMsg()()) { + .S2Tos1 => |wit| { + ref.* += 2; + s1Handler(wit, ref); + s1Handler(wit, ref); + s1Handler(wit, ref); + s1Handler(wit, ref); + }, + } +} + +``` + +由于状态机需要长期运行,在互调递归的函数中如果不使用尾递归会导致栈溢出。 + +因此上面的 Example demo 中,如果我将 20 改成很大的值,比如二百万,那么一定会发生栈溢出,因为 demo 中的调用没采用尾递归的方式。 + +在实际的 ATM 例子中他们的调用方式是: + +```zig +pub fn readyHandler(comptime w: AtmSt.EWitness(.ready), ist: *InternalState) void { + switch (w.getMsg()()) { + .ExitAtm => |witness| { + witness.terminal(); + }, + .InsertCard => |witness| { + ist.times = 0; + @call(.always_tail, cardInsertedHandler, .{ witness, ist }); + }, + } +} + +``` + +这里的 `@call(.always_tail, cardInsertedHandler, .{ witness, ist })` 就是 zig 中尾递归语法(参见:[ziglang #694](https://github.com/ziglang/zig/issues/694#issuecomment-563310662))。出于这个语法的需要,处理函数中的 Witness 被变成了编译时已知(这里是 `comptime w: AtmSt.EWitness(.ready)`)。 + +遵循这四点要求,就能获得强大的类型安全保证,足以让你愉快的使用状态机! + +# 5. 接下来能够增强的功能 + +暂时我能想到的有如下几点: + +1. 在状态机中,消息的产生和处理分开,因此可以定义多个消息产生的前端,处理部分可以任意切换消息产生的前端。比如我们可以定义测试状态机前端,用于产生测试数据,当处理部分调用测试前端的代码时就能测试整个状态机的行为。 +2. 支持子状态,这会让类型更加复杂。 +3. 开发基于[typed-fsm-zig 的 gui 系统](https://discourse.haskell.org/t/try-to-combine-typed-fsm-with-gui-to-produce-unexpected-abstract-combinations/10026),状态机在 gui 有很高的实用性,将他们结合是一个不错的选择。 +4. 开发 typed-session-zig,实现类型安全的通信协议。我在 haskell 已经实现了一个[实用的类型安全的多角色通讯协议框架](https://github.com/sdzx-1/typed-session),应该可以移植到 zig 中。 diff --git a/content/post/2025-01-23-bonkers-comptime.smd b/content/post/2025-01-23-bonkers-comptime.smd new file mode 100644 index 0000000..d156dda --- /dev/null +++ b/content/post/2025-01-23-bonkers-comptime.smd @@ -0,0 +1,391 @@ +--- +.title = "Zig comptime 棒极了", +.date = @date("2025-01-23T12:00:00+08:00"), +.author = "xihale", +.layout = "post.shtml", +.draft = false, +--- + +> 原文: + +> 译注:原文中的代码块是交互式,翻译时并没有移植。另外,由于 comptime 本身即是关键概念,并且下文的意思更侧重于 Zig comptime 的特性,故下文大多使用 comptime 代替编译时概念。 + +## 引子 + +编程通过自动化地处理数据极大地提升了生产力。而元编程则让我们可以像处理数据一样处理代码,以此将编程的力量反向作用于编程自身。而在底层编程中,我想元编程可能带来最大的优势,因为那些高级概念必须得精确映射到某些低级操作。然而,除了函数式编程语言外,我一直觉得各编程语言对元编程的实现并不理想。因此,当我看到 Zig 把元编程列为一个主要特性时,我提起了很大的兴趣。 + +说实话,刚开始使用 Zig 的 comptime 时,我的体验相当糟糕。那些概念对我而言很陌生,而想要实现预期的效果也很困难。不过后来,当我转换了思路,一切都迎刃而解了,由此,我突然就喜欢上了它。现在,为了帮助你更快地走上这条探索之路,下面我将介绍六种不同的“视角”来理解 comptime。每个视角都从不同的角度,帮助你将已有的编程知识应用到 Zig 中。 + +这并不是一本完整涵盖了 comptime 的所有所需知识的详细指南。相反,它更侧重于提供多种策略,从不同视角帮助你全面地理解该如何以 comptime 的角度思考问题。 + +为了明确起见,所有示例都是有效的 Zig 代码,但示例中的转换只是概念性的,它们并不是 Zig 实际的实现方式。 + +## 视角0: 忽略它 + +我说我喜欢这个特性,却又立刻叫你忽略它,这确实有点怪。但我认为此处正是 Zig comptime 威力所体现的地方,所以我将从这里出发。Zig Zen 中的第三条是“倾向于阅读代码,而不是编写代码。”确实,能够轻松地阅读代码在各种情况下都很重要,因为它是建立概念理解的基础,而这种理解也是调试或修改代码所必需的。 + +元编程很容易让人陷入“只写代码”的境地。如果你在使用基于宏的元编程或代码生成器,那么代码就会变成两种版本:源代码和展开后的代码。这个额外的间接层使得从阅读到调试代码的整个过程都变得更加困难。当你要改变程序的行为时,你不仅需要确定生成的代码应该是什么样的,还需要弄清楚该如何通过元编程来生成这些代码。 + +但在 Zig 中,这些额外的开销是完全不需要的。你可以简单地忽略代码在不同时间执行这一隐形的前提条件,而在概念上直接将运行时和编译时的区别忽略掉再来理解那些代码。为了演示这一点,让我们一步一步来看两个不同的代码示例。第一个是普通的运行时代码,第二个则是利用了 comptime 的代码。 + +> 普通的运行时代码 + +```zig +pub fn main() void { + const array: [3]i64 = .{1,2,3}; + var sum: i64 = 0; + for (array) |value| { + sum += value; + } + std.debug.print("array's sum is {d}.\n", .{sum}); +} +``` + +点击“下一步”逐步执行程序,观察状态的变化。这个例子很简单:对一组数字求和。现在我们来做些奇怪的事:对一个结构体的字段求和。虽然这个例子有些牵强,但却能够很好地展示这一概念。 + +> 基于 comptime 的代码 + +```zig +const MyStruct = struct { + a: i64, + b: i64, + c: i64, +}; + +pub fn main() void { + const my_struct: MyStruct = .{ + .a = 1, + .b = 2, + .c = 3, + }; + + var sum: i64 = 0; + inline for (comptime std.meta.fieldNames(MyStruct)) |field_name| { + sum += @field(my_struct, field_name); + } + std.debug.print("struct's sum is {d}.\n", .{sum}); +} +``` + +与数组求和的例子相比,这个 comptime 示例引入的新东西几乎是微不足道的。这正是 comptime 的重点!这段代码的可执行文件效率和你在 C 中为结构体类型手写一个求和函数一样高效,而它却看起来像是你在使用支持运行时反射的语言编写的。虽然这不是 Zig 实际的工作方式,但这也不完全是一个纯粹的理论练习:Zig 核心团队正在开发一个调试器,允许你像这个例子一样逐步执行混合了编译时和运行时的代码。 + +Zig 中有很多基于 comptime 且远远不止这样简单的类型反射,但你只需要阅读那些代码、完全无需深入了解其中有关 comptime 的细节就可以理解它们在干什么。当然,如果你想使用 comptime 编写代码,则不能仅仅止步于此,让我们继续深入。 + +## 视角1: 泛型 + +泛型在 Zig 中并不是一个特定的功能。相反,Zig 中的仅仅一小部分的 comptime 特性就可以提供用来处理你进行泛型编程所需的一切。这种视角虽然不能让你完全理解 comptime,但它确实为你提供了一个入口点,借此,你可以完成基于元编程的许多任务。 + +要使一个类型成为泛型,只需将其定义包裹在一个接受类型并返回类型的函数中。(译注:由于 Zig 中类型是一等公民,所以面向类型的编程是合法且常见的) + +```zig +pub fn GenericMyStruct(comptime T: type) type { + return struct { + a: T, + b: T, + c: T, + + fn sumFields(my_struct: GenericMyStruct(T)) T { + var sum: T = 0; + const fields = comptime std.meta.fieldNames(GenericMyStruct(T)); + inline for (fields) |field_name| { + sum += @field(my_struct, field_name); + } + return sum; + } + }; +} + +pub fn main() void { + const my_struct: GenericMyStruct(i64) = .{ + .a = 1, + .b = 2, + .c = 3, + }; + std.debug.print("struct's sum is {d}.\n", .{my_struct.sumFields()}); +} +``` + +泛型函数也可以如此实现。 + +```zig +fn quadratic(comptime T: type, a: T, b: T, c: T, x: T) T { + return a * x*x + b * x + c; +} + +pub fn main() void { + const a = quadratic(f32, 21.6, 3.2, -3, 0.5); + const b = quadratic(i64, 1, -3, 4, 2); + std.debug.print("Answer: {d}{d}\n", .{a, b}); +} +``` + +当然,也可以通过使用特殊类型 anytype 来推断参数的类型,而这通常在参数的类型对函数签名的其余部分没有影响时使用。(译注:此时要限制 a, b, c 的类型相同,所以此处不用 anytype ) + +## 视角2:编译时运行的标准代码 + +这是一个古老的故事: 增加一种自动执行命令的方法。当然,你还需要变量。 哦,还有条件。 拜托,能给我循环吗?这些看似合理的需求,最终导致这些自动化命令变得越来越复杂,甚至演变成一个完整的宏语言。 但 Zig 不同, 在运行时、编译时,甚至是构建系统中都使用了相同的语言。 + +考虑经典的 Fizz Buzz。 + +```zig +fn fizzBuzz(writer: std.io.AnyWriter) !void { + var i: usize = 1; + while (i <= 100) : (i += 1) { + if (i % 3 == 0 and i % 5 == 0) { + try writer.print("fizzbuzz\n", .{}); + } else if (i % 3 == 0) { + try writer.print("fizz\n", .{}); + } else if (i % 5 == 0) { + try writer.print("buzz\n", .{}); + } else { + try writer.print("{d}\n", .{i}); + } + } +} + +pub fn main() !void { + const out_writer = std.io.getStdOut().writer().any(); + try fizzBuzz(out_writer); +} +``` + +确实很简单。但是,每当讨论如何优化 Fizz Buzz 算法时,人们总是忽略一个事实:标准的 Fizz Buzz 问题只需要输出前100个数字的结果。既然输出是固定的,那为什么不直接预先计算出答案,然后输出呢?(由此,我时常认为那些有关优化讨论有些滑稽的。) +我们可以使用相同的 Fizz Buzz 函数来实现这一点。 + +```zig +pub fn main() !void { + const full_fizzbuzz = comptime init: { + var cw = std.io.countingWriter(std.io.null_writer); + fizzBuzz(cw.writer().any()) catch unreachable; + + var buffer: [cw.bytes_written]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buffer); + fizzBuzz(fbs.writer().any()) catch unreachable; + + break :init buffer; + }; + + const out_writer = std.io.getStdOut().writer().any(); + try out_writer.writeAll(&full_fizzbuzz); +} +``` + +这里的 comptime 关键字表示它后面的代码块将在编译期间运行。此外,该代码块被标记为“init”,以便整个块可以通过之后的 break 语句产出一个值。 + +我们一开始用一个 `null_writer` 来计算写入的字节数(但会丢弃实际写入的字节),以确定总长度。然后再根据该长度创建 `full_fizzbuzz` 数组来保存实际数据。 + +仅对关键部分进行计时,预计算版本的运行速度约快 9 倍。当然,这个例子过于简单,以至于总执行时间受到很多其他因素的影响,但你不难借此明白这其中 comptime 对于性能优化的意味。 + +comptime 和运行时之间有一些小的区别。比如,只有 comptime 可以访问类型为 comptime_int、comptime_float 或 type 的变量。此外,一些函数只有 comptime 参数,这使它们仅限于编译时环境。相对的,只有运行时才能进行系统调用和那些依赖系统调用的函数。如果你的代码不使用这些特性,那么它在编译时和运行时中的表现将是一样的。 + +## 视角3:[程序特化](https://en.wikipedia.org/wiki/Partial_application) + +> 译者注:程序特化(Partial Evaluation)是一种编译优化技术,主要是:在编译期预先计算部分表达式或代码路径,以减少运行时计算开销,提前生成更具体的代码实现。 + +现在我们要进入有趣的部分。 + +>译注:请参考下面的代码和代码后的解释理解这句话。 + +代码求值的一种方式是将输入替换为其运行时值,然后反复将第一个表达式替换为求值形式,直到表达式为基本元素。这在计算机科学理论上下文中很常见,在某些函数式语言中也是如此。作为后续示例的铺垫,我们将使用数组求和来展示这个过程: + + +```zig +pub fn main() void { + const array: [3]i64 = .{1,2,3}; + var sum: i64 = 0; + + for (array) |value| { + sum += value; + } + // 这可以展开为: + { + const value = array[0]; + sum += value; + } + { + const value = array[1]; + sum += value; + } + { + const value = array[2]; + sum += value; + } + + std.debug.print("array's sum is {d}.\n", .{sum}); +} +``` + +程序特化是一种可以向函数传递部分(但不一定是全部)参数的技术。 在这种情况下,可以对只使用已知值的表达式进行替换。 这样就产生了一个新函数,它只接受仍然未知的参数。 comtime 可以看作是在编译过程中进行的部分求值。 再看一下 sum 结构的例子,我们就会发现: + +```zig +onst MyStruct = struct { + a: i64, + b: i64, + c: i64, + + fn sumFields(my_struct: MyStruct) i64 { + var sum: i64 = 0; + inline for (comptime std.meta.fieldNames(MyStruct)) |field_name| { + sum += @field(my_struct, field_name); + } + + // 这可以展开为: + { + const field_name = "a"; + sum += @field(my_struct, field_name); + } + { + const field_name = "b"; + sum += @field(my_struct, field_name); + } + { + const field_name = "c"; + sum += @field(my_struct, field_name); + } + // 更进一步,有: + sum += my_struct.a; + sum += my_struct.b; + sum += my_struct.c; + + return sum; + } +}; +``` + +上面的示例是我们手动展开后的示例,但这项工作是由 Zig 的 comptime 完成的。这使得我们可以直接独立而完整地编写出我们要实现的功能,而不需要添加"当你改变 `MyStruct` 的字段时,记得更新 sum 函数"这样的由于依赖于 `MyStruct` 具体字段而预防功能失效的注释。 +基于 comptime 的版本在 `MyStruct` 的任何字段变更时都可以正确地自动处理。 + +## 视角4:Comptime 求值,运行时代码生成 + +这与程序特化(Partial Evaluation)非常相似。这里有两个版本的代码,输入(编译前)和输出(编译后)。输入代码由编译器运行。如果一个语句在编译时是可知的,它就会被直接求值。但是如果一个语句需要某些运行时的值,那么这个语句就会被添加到输出代码中。 + +让我们以数组求和为例来说明这个过程: + +> 输入这一段代码: + +```zig +const MyStruct = struct { + a: i64, + b: i64, + c: i64, + + fn sumFields(my_struct: MyStruct) i64 { + var sum: i64 = 0; + inline for (comptime std.meta.fieldNames(MyStruct)) |field_name| { + sum += @field(my_struct, field_name); + } + return sum; + } +}; +``` + +> 生成出的代码: + +```zig +const MyStruct = struct { + a: i64, + b: i64, + c: i64, + + fn sumFields(my_struct: MyStruct) i64 { + var sum: i64 = 0; + sum += my_struct.a; + sum += my_struct.b; + sum += my_struct.c; + return sum; + } +}; +``` + +这实际上是最接近 Zig 编译器处理 comptime 的方式。他们的主要区别在于 Zig 首先解析你的代码的语法,并将其转换为虚拟机的字节码。这个虚拟机的运行方式就是 comptime 的实现方式。这个虚拟机将估量它能处理的所有内容,并为需要运行时处理的内容生成新的字节码(稍后将其转换为机器码)。具有运行时输入的条件语句,如 if 语句,会直接输出两条路径。 + +自然,这样做的后果是死代码永远不会被语义分析。也就是说,一个无效的函数并不总是会在实际被使用之前产出相应的编译错误。(对此你可能需要适应一段时间)然而,这也使得编译更加高效(译注:部分地弥补了 Zig 暂不支持增量编译的缺陷),并允许更自然的外观条件编译,这里没有 `#ifdef` (译注:谢天谢地~)! + +值得注意的是, comptime 在 Zig 的设计中是个很基本的设计, 所有的 Zig 代码都通过这个虚拟机运行,包括没有明显使用 comptime 的函数。 即使是简单的类型名称,如函数参数,实际上也是在 comptime 中评估类型变量的表达式。 这就是上面泛型示例的工作原理。 这也意味着您可以酌情使用更复杂的表达式来计算类型。 + +这样做的另一个后果是,Zig 代码的静态分析要比大多数静态类型语言复杂得多,因为编译器需要运行很大一部分才能确定所有类型。 因此,在 Zig 工具链跟上之前,代码自动补全等编辑工具并不总是能很好地发挥作用。 + +## 视角5:直接生成代码(Textual Code Generation) + +我在文章开头感叹元编程难度。然而,即使在 Zig 中,它仍然是一个强大的工具,在解决某些问题方面也占有一席之地。如果您熟悉这种元编程方法,对 Zig comptime 提供的功能可能会觉得有些残缺。比如, 怎么在写一段代码在运行时能够生成新代码? + +但等等,上一个例子不就是这样吗? 如果你以正确的方式看待问题,写代码的代码和混合运行时代码之间存在着潜在的等价关系。 + +下有两例。第一个是一个元编程的示例,第二个是我们熟悉的 comptime 示例。这两个版本的代码有着相同的逻辑。 + +```zig +pub fn writeSumFn( + writer: std.io.AnyWriter, + type_name: []const u8, + field_names: [][]const u8, +) !void { + try writer.print("fn sumFields(value: {s}) i64 {{\n", .{type_name}); + try writer.print("var sum: i64 = 0;\n", .{}); + for (field_names) |field_name| { + try writer.print("sum += value.{s};\n", .{field_name}); + } + try writer.print("return sum;\n", .{}); + try writer.print("}}\n", .{}); +} +``` + +注意这里有两个转换: +1. 在生成器中直接运行的代码是 comptime 的一部分 +2. 在生成器执行后输出的代码,成为运行时的一部分 + + +我喜欢这个示例的另一点是,它展示了在 Zig 中使用类型信息作为输入来生成代码是多么简单。这个例子略过了类型名称和字段名称信息的来源。如果你使用其他形式的输入,比如 Zig 提供了 `@embedFile`,你可以像平常一样解析它。 + +回到泛型的例子,有一些值得强调的细微之处: + +```zig +pub fn writeMyStructOfType( + writer: std.io.AnyWriter, + T: []const u8, +) !void { + try writer.print("const MyStruct_{s} = struct {{\n", .{T}); + try writer.print("a: {s},\n", .{T}); + try writer.print("b: {s},\n", .{T}); + try writer.print("c: {s},\n", .{T}); + + try writer.print("fn sumFields(value: MyStruct_{s}) {s} {{\n", .{T,T}); + try writer.print("var sum: {s} = 0;\n", .{T}); + const fields = [_][]const u8{ "a", "b", "c" }; + for (fields) |field_name| { + try writer.print("sum += value.{s};\n", .{field_name}); + } + try writer.print("return sum;\n", .{}); + try writer.print("}}\n", .{}); + try writer.print("}};\n", .{}); +} +``` + +以上 struct 字段的生成体现了上述两种转换方式,并且将两者混合在了一行中。 字段的类型表达式由生成器/运行时完成,而字段本身则作为运行时代码使用的定义。 + +在 comptime 下,引用类型名称的方式更加直接,可以直接使用函数,而不必将文本拼接成一个在代码生成中保持一致的名称。 + +这种观点有一个例外。 您可以创建字段名称在编译时就已确定的类型,但这样做需要调用一个内置函数,该函数包含一个字段定义列表。 因此,您无法在这些类型上定义方法等声明。 在实践中,这并不会限制代码的表达能力,但确实限制了你可以向其他代码公开哪些类型的 API。 + +与本节相关的是文本宏,如 C 语言中的文本宏。你可以做的大多数正常事情都可以在 comptime 中完成,尽管它们很少采用类似的形式。 不过,文本宏并不能做所有允许做的事情。 例如,你不能决定不喜欢某个 Zig 关键字,然后让宏代替你自己的关键字。 我认为这是一个正确的决定,尽管对于那些习惯了这种能力的人来说,这是一个艰难的过渡。 此外,Zig 参考了半个世纪以来的程序员在这方面的探索,所以它的选择要理智得多。 + +## 结论 + +在阅读 Zig 代码以理解代码行为时,考虑 comptime 并不是必要的。而当编写 comptime 代码时,我通常会将其视为程序特化(Partial Evaluation)的一种形式。然而,如果你知道如何使用不同的元编程方法解决问题,你很可能有能力将其翻译成 comptime 形式。 + +元编程中直接生成代码的方法的存在,就是我全力支持 Zig 风格的 comptime 元编程的原因。尽管,直接生成代码是几乎是最强大的,但是,在阅读和调试时忽略 comptime 的特性的元编程方法确是最简单的。正因如此,我给本文取名为《Zig comptime 棒极了》。 + +## 进一步阅读 + +Zig 并非一个仅仅依赖 comptime 这一特性的语言。你可以在[官方网站](https://ziglang.org/)上了解更多关于 Zig 的信息。 + +在这篇文章中,我多次使用相同的例子来展示不同的转换方式(代码->编译时和运行时),以简化展示的过程。这样做的缺点是,尽管谈论了很多,但实际上我并没有展示太多相关的内容。而[语言参考文档](https://ziglang.org/documentation/0.13.0/)详细介绍了编译时的具体特性。 + +如果您想看到更多示例,我建议您阅读一些 Zig 的标准库代码。以下是一些供有兴趣者参考的链接: + +- [std.debug.print](https://github.com/ziglang/zig/blob/0.13.0/lib/std/fmt.zig#L80) 是一个强大的泛型函数。许多语言在运行时解析它们的格式字符串,并很可能为字符串格式添加了一些特殊的效验器,以尽早捕获错误。而在 Zig 中,格式字符串是在编译时解析的,这样不仅生成了高效的最终代码,还在编译时完成了所有的校验。 +- [ArrayList](https://github.com/ziglang/zig/blob/0.13.0/lib/std/array_list.zig#L25) 是一个实现相对简单但功能齐全的泛型容器。 + +Zig 的函数可以具有几种不同的返回类型。但是,这并不是依赖于编译器中的某些魔法的操作,而只是[典型的 comptime 的应用](https://github.com/ziglang/zig/blob/0.13.0/lib/std/start.zig#L508)。 + +> 如果您希望就本篇文章向我提出意见或更正,请发送电子邮件至 blogcomments@scottredig.com。 +> 译者注:如果觉得翻译有问题,请提 PR 改正: diff --git a/content/post/first-post/fanzine.jpg b/content/post/first-post/fanzine.jpg deleted file mode 100644 index 9d6e569f9c2dc06fa61d7b2ebb8e3c8fe4c871fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124852 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�D!G8oJ(CKWoIR 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%0DwhC-?>Fc_(wk|?kH8I^_L0u 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
b~bQm);V|c7yw91 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 diff --git a/content/post/first-post/index.smd b/content/post/first-post/index.smd deleted file mode 100644 index 4d6c70b..0000000 --- a/content/post/first-post/index.smd +++ /dev/null @@ -1,110 +0,0 @@ ---- -.title = "First Post: What's A Zine?", -.date = @date("1990-01-01T00:00:00"), -.author = "ZigCC", -.layout = "post.shtml", -.draft = false, ---- - -This is a sample first post for your blog. - -This post is defined in `content/blog/first-post/` and contains the following -files: - -- `index.smd` -- `fanzine.jpg` - -Another interesting thing about this post is that it uses the `layouts/post.shtml` -template which adds at the bottom page navigation within the blog section. - -Enough about Zine, let's talk about zines. - -## Fanzines - -A zine (short for *fanzine*, from "fan" + "magazine") is a non-professional -publication created by people that want to express themselves in paper form, -usually in relation to a cultural phenomenon of some kind. - -[An example of zine from 1976.](<$image.asset("fanzine.jpg").alt("A photo of a black and white flyer. The main title reads 'A night of pure energy' and the rest of the page contains a mix of pictures of guitarists interleaved with other text snippets, some seemingly taken from a professional publication of some kind, and some handwritten to announce concert night dates, presumably for the musicians in the picture.")>) - -## Zines in the digital era - -In the digital age, some of the cultural impetus behind zines got redirected to -personal blogs and similar digital, non-professional publications. The 90's and -early 00's are famous for their whacky websites full of clip art, wordart text -and "under construction" animated gifs. - -This initial organic exploration didn't last for too long as the rise of social -media diverted a lot of self-expression energy towards walled gardens, a change -that was also fueled by the much tighter and intense feedback loop that those -platform enable. - -In the 90's the stongest dopamine hit you could get was adding a visit counter -to your altavista website and watch it go up, slowly, mostly because of your own -accesses. In modern times views are almost meaningless and interactions such as -*likes*, *retweets* and *comments* provide much stronger positive feedback. - -An unfortunate side effect of this new cultural wave centered around social media -is that not only you end up gifting your content to platform owners, but you also -participate in a system where the *language* of the social media site shapes your -thoughts and experience in specific, and often user-hostile, ways. - -Art, just like liquids, takes the shape of the container you put it in. A mobile -game that lives off of in app purchases will never be truly great because of the -tension between making the game entertaining enough to keep players engaged, and -the need to make it boring enough so that they will want to buy upgrades to make -the game more fun. - -Similarly, self-expression on Twitter is encouraged to take the shape of short, -hyperbolic hot takes that forgo nuance in order to create catchy quips that can -be used for hasty decision making. - -Likewise, [corpowave]($text.attrs('wave')) never goes out of style on LinkedIn. -Spend enough time in there and you will become a character from Severance. - -## We're not in 1990 anymore - -Despite all the issues with social media, there is no point in thinking of the -90's as a better time. It was not. And despite the winks at the past, Zine is -not a tool for indulging in nostalgia. - -**The goal is to make art**: the act of inducing a change in others through -our self-expression. - -You could argue that the 90's excelled at self-expression, but in doing so you -would also have to accept that social media is infinitely more effective at -inducing change in others (albeit at the expense of freedom of expression). - -Once you realize that, the path forward is clear: - -1. Own your content. -2. Create new social systems that optimize for creating art over engagement. - -Owning your content means that you will be unaffected by enshittification of -platforms that would otherwise keep your data hostage. It also is the single -most effective thing you can do as an individual to take power away from -platforms, all while protecting your own immediate interests. - -Creation of new social systems is a *slightly more hairy* problem than self -hosting a static website, but it's something that can be done. Over the years -we've had plenty of social outlets that have allowed people to socialize through -their homemade games, music, drawings, fanfics, etc; and chances are that we -have yet many more of these outlets ahead of us to create. - -Zine gives you a small puzzle piece to help you inch closer toward a better -future, partially by providing you with a new iteration over tried and true -patterns (e.g. by facilitating content creation by separating content from -layouting concerns as much as possible), and also by being a bit experimental -with the concept of a devlog, something that you wouldn't normally expect to -find on a static website. - -Lastly, Zine makes sure your content (both blog and devlog, but also any -other content format you might come up with yourself) is available via RSS -syndication. RSS feeds are far from a winning technology in the fight against -the ebb and *enshitty*flow of social media, but they are another small puzzle -piece that costs nothing to maintain and that might turn out to be critical once -enough other preconditions are met. - -With that in mind, **go make art with your words**. - --- Loris diff --git a/content/post/index.smd b/content/post/index.smd index 4925d42..69b3f06 100644 --- a/content/post/index.smd +++ b/content/post/index.smd @@ -1,40 +1,9 @@ --- -.title = "Blog", -.date = @date("1990-01-01T00:00:00"), +.title = "博客", +.date = @date("2024"), .author = "ZigCC", .layout = "post.shtml", -.alternatives = [{ - .name = "rss", - .layout = "post.xml", - .output = "index.xml", -}], .draft = false, ---- - -This page defines the blog section and lists all posts in it. - -A "site section" in Zine is a group of pages that form a logical subtree of the -website. It's related to directory structure, but it's not an entirely 1:1 mapping. - -What defines a site section in Zine is the presence of `index.smd` files. You -can learn more [in the official Zine docs](https://zine-ssg.io/docs/). - -Take also a look at `layouts/blog.shtml` to get an idea of how to render a page -list in a SuperHTML template. - -The blog section also has an [RSS feed]($link.alternative('rss')). - -In Zine, RSS feeds are considered "alternative" versions of an existing page. In -concrete defines the blog section and that lists all pages in it, is rendered in -two versions: HTML for human readers, and XML for RSS readers. - -This is the SuperMD frontmatter code that defines the RSS feed: +--- -```ziggy -.alternatives = [{ - .name = "rss", - .layout = "rss.xml", - .output = "index.xml", -}], -``` -[(btw syntax highlighting is done statically in Zine, no need for javascript libraries, unless you want to)]($text.attrs('small')) +欢迎大家向我们投稿,会同步到微信公众号,投稿方式见[这里](./2023-09-05-hello-world)。 diff --git a/content/post/news/2023-12-11-first-meetup.smd b/content/post/news/2023-12-11-first-meetup.smd new file mode 100644 index 0000000..aa44926 --- /dev/null +++ b/content/post/news/2023-12-11-first-meetup.smd @@ -0,0 +1,53 @@ +--- +.title = "Zig 语言中文社区第一次线上会议", +.date = @date("2023-12-11T08:25:00+0800"), +.author = "ZigCC", +.layout = "post.shtml", +.draft = false, +--- + +2023 年 12 月 9 日,Zig 中文社区第一次线上会议隆重召开。共有 8 位 Zig +爱好者参加,分布在北上杭成、美国等不同地方。 + +{{\< figure src="/images/first-online-meeting.webp" +caption="会议参会人员"\>}} + +和当年的从仙童半导体出逃的人数一样,不多不少。😄 {{\< figure +src="/images/fair-children.webp" caption="硅谷八叛徒"\>}} +会议伊始,成员首先进行了个人简介,便于后续开展相应工作。随后,社区成员围绕 +Zig 语言的普及进行了交流讨论。 + +在交流讨论环节,大家就 Zig +语言的普及面临的挑战和机遇进行了深入的探讨。其中,大家认为 Zig +语言的普及面临的主要挑战包括: + +- Zig 语言是一个新兴的语言,知名度还不够高。 +- Zig 语言的生态还不够完善,缺乏成熟的库和工具。 + +与此同时,大家也认为 Zig 语言的普及也具有一定的机遇,包括: + +- Zig 语言具有很强的性能、安全性和易用性,具有一定的竞争力。 +- Zig 语言的设计理念与 C 语言类似,对于 C + 语言开发者来说具有较高的学习成本。 + +因此,第一阶段,我们打算推出一系列教程来帮助大家学习 +Zig,目前主要有以下几个: + +| 项目 | 参与人员 | 目标 | 仓库 | +|------------------|----------------|-------------------------------------------------------------------------------|---------------------------------------------------------------------------------| +| Zig 入门教程 | 金中甲 | 让没有编程背景的人可以有体系的学习 Zig | [learnzig/learnzig](https://github.com/learnzig/learnzig) | +| Zig 教学视频 | Onion、Lambert | 同上,素材取自 [Learning Zig 中文翻译](https://zigcc.github.io/learning-zig/) | | +| Zig cookbook | 夜白、冯文轩 | 演示如何用 Zig 做某个功能 | [zigcc/zig-cookbook](https://github.com/zigcc/zig-cookbook) | +| Zig 构建系统教程 | 贺鹏、陈瑞 | 体验 Zig 编译系统的能力与优势、与其他构建系统的对比 | [zigcc 网站系列文章](https://zigcc.github.io/post/) | +| Zig 写 OS 教程 | 柠檬、西瓜 | 体现 Zig low level 的优势 | [zigcc/how-to-write-os-in-zig](https://github.com/zigcc/how-to-write-os-in-zig) | +| Zig 惯用法 | 全体 | 收集 Zig 编程技巧 | [zigcc/zig-idioms](https://github.com/zigcc/zig-idioms) | + +我们希望通过这些努力,提高 Zig 语言的知名度,完善 Zig 语言的生态,促进 +Zig 语言的交流和学习。 + +# 结论 + +Zig 中文社区第一次线上会议的召开,标志着 Zig +社区正式启航。如果读者对共建社区感兴趣,欢迎与我们联系。 + +- 邮箱:zig@liujiacai.net diff --git a/content/post/news/2023-12-27-second-meetup.smd b/content/post/news/2023-12-27-second-meetup.smd new file mode 100644 index 0000000..e3753d0 --- /dev/null +++ b/content/post/news/2023-12-27-second-meetup.smd @@ -0,0 +1,70 @@ +--- +.title = "ZigCC 第二次线上会议", +.date = @date("2023-12-27T08:39:51+0800"), +.author = "ZigCC", +.layout = "post.shtml", +.draft = false, +--- + +2023-12-23,ZigCC 社区开始了第二次线上会议,共有 5 名 Zig +爱好者参加,分别是: + +- [西瓜](https://github.com/jiacai2050/) +- [贺鹏](https://github.com/xnhp0320) +- [Lambert](https://github.com/labspc) +- [冯文轩](https://github.com/fwx5618177) +- [Reco](https://github.com/1000copy) + +这次会议主要是同步了之前会议落实的 +action,主要是同步了不同项目的进展,由于临近年底,大家进度都不算太大,但还是有所进展,算是开了个好头😄 + +# 项目进展 + +## [Zig-OS](https://github.com/zigcc/zig-os) + +- 主要参与人员:西瓜 +- 进展:粗略看完 rust 版本的教程;完成 freestanding 二进制,现在卡在了 + bootloader 阶段 + +## [Learn zig](https://github.com/learnzig/learnzig) + +- 主要参与人员:金中甲 +- zig的进阶特性,诸如构建系统、包管理、与C交互均已完成,目前教程内容已基本覆盖日常使用 +- 增加了评论区的功能 +- 待完成:反射(编译期反射和运行时反射)、内建函数说明(包含使用例子)、未定义行为、wasm、原子操作这些边缘部分 + +## Zig 教学视频 + +- 主要参与人员:Lambert +- +- 暂无明显进展 + +## [Zig cookbook](https://github.com/zigcc/zig-cookbook) + +- 主要参与人员:夜白、西瓜 +- 已经完成大部分内容 👍 + +## Zig 构建系统教程 + +- 主要参与人员:Reco +- 目前主要是对 [zig build + explained](https://zig.news/xq/zig-build-explained-part-3-1ima) + 系列文章翻译 + +# 新人介绍 + +在第一次会议后,有一些朋友想加入 ZigCC +社区,经过简单筛选,新增一名成员:Reco,下面是他的一些履历: + +- 南美 Optimes co.,limited 联合创始人、CTO +- 任我行软件股份有限公司 集团CTO + +其他技术兴趣经历 + +1. 图灵出版社区签约作者。4本电子系列书:《Vue.js小书》《Git小书》《HTTP小书》《Swift + iOS开发小书》 +2. 微软 DotNet 技术俱乐部 2007-2010年成都地区主席 +3. + +非常欢迎 Reco 的加入!也希望更多对 Zig 感兴趣的朋友加入我们,普及 Zig +在中文社区内的使用。联系邮箱:zig@liujiacai.net diff --git a/content/post/news/2024-01-14-third-meetup.smd b/content/post/news/2024-01-14-third-meetup.smd new file mode 100644 index 0000000..aa903d2 --- /dev/null +++ b/content/post/news/2024-01-14-third-meetup.smd @@ -0,0 +1,62 @@ +--- +.title = "ZigCC 第三次线上会议", +.date = @date("2024-01-14T09:20:41+0800"), +.author = "西瓜", +.layout = "post.shtml", +.draft = false, +--- + +在 2024-01-13 晚,ZigCC 社区举行了第三次线上会议,参会人员: + +- [西瓜](https://github.com/jiacai2050/) +- [Lambert](https://github.com/labspc) +- [金中甲](https://github.com/jinzhongjia) +- [夜白](https://github.com/byte911) + +会议主要讨论了下面两个议题: + +- 公众号运营 +- 如何与其他社区互动 + +# 公众号运营 + +这是最近群里聊到的问题,由于 Zig +语言本身属于较新的技术,因此社区内资料比较少,这导致很多感兴趣的人没有一个好的学习途径。 + +但对中文环境来说,我们其实之前已经积攒了一些素材,是完全可以通过公众号的形式进行传播的,主要来源: + +- [Learning Zig 中文翻译](https://zigcc.github.io/learning-zig/) +- [Zig 语言圣经](https://zigcc.github.io/zig-course/) +- [Zig Cookbook](https://zigcc.github.io/zig-cookbook/) + +目前可以按照 Rust +日报的方式,每日截取其中的片段进行发送,方便读者在闲暇浏览阅读;另一方面,公众号也会介绍 +[awesome-zig](https://github.com/zigcc/awesome-zig) +中的实际项目,同步他们的进展。 + +虽然名字是『Zig 日报』,但应该不会每天都发,毕竟 Zig +社区还比较年轻,但估计间隔不会超过 3 +天,看后续运行实际效果再来调整频率。 + +主要参与人员:西瓜、金中甲 + +# 社区互动 + +目前我们的成员在 Zig +的实践方面相对较少,因此决定目前不过多的去宣传,在积攒了一些实际项目经验后,再来考虑。 + +# 欢迎更多朋友加入 ZigCC + +现在回看,距离第一次 ZigCC 线上会议过了一个月,经过 ZigCC +成员的努力,还是交出了一份比较满意的答卷,[cookbook](https://github.com/zigcc/zig-cookbook) +项目斩获 400+ 的⭐️,而且我们也有了新的 +[logo](https://github.com/zigcc/logo),另外要感谢金中甲同学,他把之前自己写的教程捐给了 +ZigCC,质量非常高,因此我们决定把他重命名为 [Zig +语言圣经](https://zigcc.github.io/zig-course/),熟悉 Rust +的朋友可能会知道原因。😃 + +也许在读文章的你也在犹豫是否能加入,担心没有 Zig +经验是否会有影响,其实这都不是核心,现在的成员也没说 Zig +经验有多丰富,只要有踏实做事的心态,愿意帮助他人即可,Zig +可以慢慢学,有想法的朋友可以邮件到 zig@liujiacai.net +,简单自我介绍,之后我会拉到对应群组中,便于开展后续的工作。 diff --git a/content/post/news/2024-04-27-release-party-review.smd b/content/post/news/2024-04-27-release-party-review.smd new file mode 100644 index 0000000..52c3ef1 --- /dev/null +++ b/content/post/news/2024-04-27-release-party-review.smd @@ -0,0 +1,58 @@ +--- +.title = "0.12.0 Release Party 回顾", +.date = @date("2024-04-28T09:53:45+0800"), +.author = "Jiacai Liu", +.layout = "post.shtml", +.draft = false, +--- + +2024-04-20,0.12.0 终于发布了,历时 8 个月,有 268 位贡献者,一共进行了 +3688 次提交!下面是它的 Release notes: + +- + +ZigCC 对这个文档进行了翻译、整理,供需要升级适配的朋友参考: + +- [0.12.0 升级指南](https://course.ziglang.cc/update/upgrade-0.12.0) +- [0.12.0 版本说明](https://course.ziglang.cc/update/0.12.0-description) + +为了庆祝这一盛事,ZigCC 决定在 2024-04-27 +举行了一次线上的发行聚会,主要来讨论这次的版本,下面是视频回看地址: + +- +- + +在这次会议上,主要讨论了两部分内容: + +第一是构建系统,0.12.0 版本对用户来说,主要是稳定了构建系统的 +API,这对于 Zig 生态的构建十分重要,如果某用户写了一个基础库,但是升级 +Zig 版本后,就没法编译了,可以想象,这是很沮丧的事情。 + +Zig 的构建系统分为两部分: + +- zon 文件,声明依赖, `zig fetch` 会去下载里面的依赖 +- `build.zig` 文件,项目的构建器,由多个 Step + 形成一个有向无环图,来驱动不同逻辑的进行,如安装头文件、编译静态链接库等。Step + 里面最重要的是 Compile ,addTest、addExecutable + 返回的都是它,主要功能是对代码进行编译。其他常见的 Step 还有 + - ConfigHeader 配置要用的头文件 + - InstallArtifact,将编译好的 lib 或 bin 安装到 zig-out 目录中 + +第二个是自己写的 x86 的后端,它可以不依赖 llvm +直接生成可以执行的汇编代码,这也是 [make the main zig executable no +longer depend on LLVM, LLD, and Clang libraries +\#16270](https://github.com/ziglang/zig/issues/16270) 这个 issue +的基础。之前笔者以为所谓移除 llvm,是把 Zig 代码翻译成 C +代码,然后再有不同架构下的 C +编译器来生成最终的可执行文件,目前看这种想法是错误的, 尽管 Zig 有 C +这个后端,但目前看并不是解决这个 issue 专用的。 + +这就不得不好奇,Zig +团队难道要把生成所有架构下的二进制?还是说对于用的少的架构,[直接生成 +llvm 的 bc +文件](https://github.com/ziglang/zig/issues/13265),然后剩下的活再交给 +llvm 去做? +目前笔者还没有十分明确的答案,希望今后能尽快搞清楚这个问题,也欢迎了解的读者留言指出。 + +稍微遗憾的是这次参会的朋友基本都还是处于观望阶段,希望下次能有些具体项目经验可以聊,See +you next time! diff --git a/content/post/news/index.smd b/content/post/news/index.smd new file mode 100644 index 0000000..8f3a05e --- /dev/null +++ b/content/post/news/index.smd @@ -0,0 +1,17 @@ +--- +.title = "社区新闻", +.date = @date("2024-08-03T16:43:51+0800"), +.author = "ZigCC", +.layout = "post.shtml", +.draft = false, +--- + +我们会不定期举行线上会议来畅聊 +Zig,感兴趣的朋友可以通过下面日历查看,或订阅[这个 +iCalendar](https://calendar.yandex.com/export/ics.xml?private_token=71fd8e02d7944f4e7ae44cc8a9b8877da9e9f2f1&tz_id=Asia/Hong_Kong)。 + +```=html + +``` + +线上会议地址:https://discord.gg/36C7H47t47?event=1304329702512787466 diff --git a/content/post/second-post.smd b/content/post/second-post.smd deleted file mode 100644 index d1b1825..0000000 --- a/content/post/second-post.smd +++ /dev/null @@ -1,28 +0,0 @@ ---- -.title = "Second Post", -.date = @date("1990-01-02T00:00:00"), -.author = "ZigCC", -.layout = "post.shtml", -.draft = false, ---- - -This second post is mainly here to show you that you can also create single file -posts for convenience. The first post contains more interesting content. - -Don't forget to read [the official SuperMD -docs](https://zine-ssg.io/docs/supermd/) to know how to *style* your content. - - -Btw this sample website also includes the JS/CSS dependencies required to render -math: - -```=mathtex -\begin{aligned} -f(t) &= \int_{-\infty}^\infty F(\omega) \cdot (-1)^{2 \omega t} \mathrm{d}\omega \\ -F(\omega) &= \int_{-\infty}^\infty f(t) \div (-1)^{2 \omega t} \mathrm{d}t \\ -\end{aligned} -``` - -This: [`(-1)^x = \cos(\pi x) + i\sin(\pi x)`]($mathtex) is an inline equation -instead! - diff --git a/convert_md_to_smd.js b/convert_md_to_smd.js index 50c57d9..c9f7f88 100644 --- a/convert_md_to_smd.js +++ b/convert_md_to_smd.js @@ -1,150 +1,157 @@ #!/usr/bin/env node -const fs = require('fs'); -const path = require('path'); +const fs = require("fs"); +const path = require("path"); // 配置 const config = { - sourceDir: './content/learn', - author: 'Karl Seguin; ZigCC', - time: '2024', - layout: 'learn.shtml' + sourceDir: "./content/post/news", + author: "ZigCC", + time: "2024", + layout: "post.shtml", }; -// 转换markdown frontmatter到smd格式 +// 转换 markdown frontmatter 到 smd 格式 function convertFrontmatter(content) { - const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/); - if (!frontmatterMatch) { - return content; - } + const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/); + if (!frontmatterMatch) { + return content; + } - const frontmatter = frontmatterMatch[1]; - const body = content.replace(/^---\n[\s\S]*?\n---\n/, ''); - - // 解析现有的frontmatter - const lines = frontmatter.split('\n'); - const metadata = {}; - - for (const line of lines) { - const match = line.match(/^(\w+):\s*(.+)$/); - if (match) { - metadata[match[1]] = match[2].replace(/^["']|["']$/g, ''); // 移除引号 - } - } + const frontmatter = frontmatterMatch[1]; + const body = content.replace(/^---\n[\s\S]*?\n---\n/, ""); + + // 解析现有的 frontmatter + const lines = frontmatter.split("\n"); + const metadata = {}; - // 构建新的smd frontmatter - const newFrontmatter = [ - '---', - `.title = "${metadata.title || 'Untitled'}",`, - `.date = @date("2024-01-01T00:00:00"),`, - `.author = "${config.author}",`, - `.layout = "${config.layout}",`, - `.draft = false,`, - '---', - '' - ].join('\n'); - - return newFrontmatter + body; + for (const line of lines) { + const match = line.match(/^(\w+):\s*(.+)$/); + if (match) { + metadata[match[1]] = match[2].replace(/^["']|["']$/g, ""); // 移除引号 + } + } + + let hasCustomFrontmatter = Object.keys(metadata).length > 3; + + // 构建新的 smd frontmatter + const newFrontmatter = [ + "---", + `.title = "${metadata.title || "Untitled"}",`, + `.date = @date("${metadata.date || config.time}"),`, + `.author = "${metadata.author || config.author}",`, + `.layout = "${config.layout}",`, + `.draft = false,`, + hasCustomFrontmatter?`.custom = {\n${Object.entries(metadata) + .filter(([k]) => !["title", "date", "author"].includes(k)) + .map(([k, v]) => ` .${k} = "${v}",`) + .join("\n")}\n},\n---`:"---", + "", + ].join("\n"); + + return newFrontmatter + body; } -// 转换markdown链接格式 +// 转换 markdown 链接格式 function convertLinks(content) { - // 转换 {{< ref "filename.md" >}} 格式到相对链接 - content = content.replace(/\{\{<\s*ref\s+"([^"]+\.md)"\s*>\}\}/g, '[$1]($1)'); - - // 转换其他可能的markdown链接格式 - // 这里可以根据需要添加更多转换规则 - - return content; + // 转换 {{< ref "filename.md" >}} 格式到相对链接 + content = content.replace(/\{\{<\s*ref\s+"([^"]+\.md)"\s*>\}\}/g, "[$1]($1)"); + + // 转换其他可能的 markdown 链接格式 + // 这里可以根据需要添加更多转换规则 + + return content; } // 处理单个文件 function processFile(filePath) { - try { - const content = fs.readFileSync(filePath, 'utf8'); - const convertedContent = convertLinks(convertFrontmatter(content)); - - // 创建新的smd文件路径 - const dir = path.dirname(filePath); - const basename = path.basename(filePath, '.md'); - const newPath = path.join(dir, `${basename}.smd`); - - // 写入新文件 - fs.writeFileSync(newPath, convertedContent, 'utf8'); - console.log(`✓ Converted: ${filePath} -> ${newPath}`); - - return true; - } catch (error) { - console.error(`✗ Error processing ${filePath}:`, error.message); - return false; - } + try { + const content = fs.readFileSync(filePath, "utf8"); + const convertedContent = convertLinks(convertFrontmatter(content)); + + // 创建新的 smd 文件路径 + const dir = path.dirname(filePath); + const basename = path.basename(filePath, ".md"); + const newPath = path.join(dir, `${basename}.smd`); + + // 写入新文件 + fs.writeFileSync(newPath, convertedContent, "utf8"); + console.log(`✓ Converted: ${filePath} -> ${newPath}`); + + return true; + } catch (error) { + console.error(`✗ Error processing ${filePath}:`, error.message); + return false; + } } // 递归处理目录 function processDirectory(dirPath) { - try { - const items = fs.readdirSync(dirPath); - let successCount = 0; - let totalCount = 0; - - for (const item of items) { - const fullPath = path.join(dirPath, item); - const stat = fs.statSync(fullPath); - - if (stat.isDirectory()) { - // 递归处理子目录 - const result = processDirectory(fullPath); - successCount += result.success; - totalCount += result.total; - } else if (item.endsWith('.md')) { - // 处理markdown文件 - const success = processFile(fullPath); - if (success) successCount++; - totalCount++; - } - } - - return { success: successCount, total: totalCount }; - } catch (error) { - console.error(`✗ Error processing directory ${dirPath}:`, error.message); - return { success: 0, total: 0 }; + try { + const items = fs.readdirSync(dirPath); + let successCount = 0; + let totalCount = 0; + + for (const item of items) { + const fullPath = path.join(dirPath, item); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + // 递归处理子目录 + const result = processDirectory(fullPath); + successCount += result.success; + totalCount += result.total; + } else if (item.endsWith(".md")) { + // 处理 markdown 文件 + const success = processFile(fullPath); + if (success) successCount++; + totalCount++; + } } + + return { success: successCount, total: totalCount }; + } catch (error) { + console.error(`✗ Error processing directory ${dirPath}:`, error.message); + return { success: 0, total: 0 }; + } } // 主函数 function main() { - console.log('🚀 Starting markdown to smd conversion...'); - console.log(`📁 Source directory: ${config.sourceDir}`); - console.log(`👤 Author: ${config.author}`); - console.log(`📅 Time: ${config.time}`); - console.log(''); - - if (!fs.existsSync(config.sourceDir)) { - console.error(`✗ Source directory does not exist: ${config.sourceDir}`); - process.exit(1); - } - - const result = processDirectory(config.sourceDir); - - console.log(''); - console.log('📊 Conversion Summary:'); - console.log(`✓ Successfully converted: ${result.success}/${result.total} files`); - - if (result.success === result.total) { - console.log('🎉 All files converted successfully!'); - } else { - console.log('⚠️ Some files failed to convert'); - } + console.log("🚀 Starting markdown to smd conversion..."); + console.log(`📁 Source directory: ${config.sourceDir}`); + console.log(`👤 Author: ${config.author}`); + console.log(`📅 Time: ${config.time}`); + console.log(""); + + if (!fs.existsSync(config.sourceDir)) { + console.error(`✗ Source directory does not exist: ${config.sourceDir}`); + process.exit(1); + } + + const result = processDirectory(config.sourceDir); + + console.log(""); + console.log("📊 Conversion Summary:"); + console.log( + `✓ Successfully converted: ${result.success}/${result.total} files` + ); + + if (result.success === result.total) { + console.log("🎉 All files converted successfully!"); + } else { + console.log("⚠️ Some files failed to convert"); + } } // 运行脚本 if (require.main === module) { - main(); + main(); } module.exports = { - convertFrontmatter, - convertLinks, - processFile, - processDirectory -}; \ No newline at end of file + convertFrontmatter, + convertLinks, + processFile, + processDirectory, +}; diff --git a/layouts/monthly.shtml b/layouts/monthly.shtml index 5ce6ac0..e44aab8 100644 --- a/layouts/monthly.shtml +++ b/layouts/monthly.shtml @@ -8,7 +8,7 @@
  • -
    +
    ← diff --git a/layouts/post.shtml b/layouts/post.shtml index db6c5b4..f588f3c 100644 --- a/layouts/post.shtml +++ b/layouts/post.shtml @@ -1,29 +1,20 @@

    -
    -
    +
    +

    +

    +
    +
    +
    +
    ← From 9a85dd3fa7782afe1ce9ed1b398875d14f1fea53 Mon Sep 17 00:00:00 2001 From: xihale Date: Tue, 1 Jul 2025 17:57:54 +0800 Subject: [PATCH 03/22] Add new CSS styles for code highlighting, update navigation arrows, and include SVG icons for left and right arrows. Create a cache file for ggshield secrets detection. Update Zig code block in documentation and enhance community news section in posts. --- .cache_ggshield | 1 + assets/_highlight.css | 265 ++++++++++++++++++++ assets/highlight.css | 298 +++-------------------- assets/icons/arrow-left.svg | 18 ++ assets/icons/arrow-right.svg | 18 ++ assets/style.css | 9 + content/learn/03-language-overview-1.smd | 2 +- content/post/index.smd | 2 + layouts/learn.shtml | 2 - main | 2 +- 10 files changed, 348 insertions(+), 269 deletions(-) create mode 100644 .cache_ggshield create mode 100644 assets/_highlight.css create mode 100644 assets/icons/arrow-left.svg create mode 100644 assets/icons/arrow-right.svg diff --git a/.cache_ggshield b/.cache_ggshield new file mode 100644 index 0000000..ceb7666 --- /dev/null +++ b/.cache_ggshield @@ -0,0 +1 @@ +{"last_found_secrets": [{"match": "95eb7c12d136f77a6541bb255a8d9448a52bff447fb8216441841e4b7fd75a89", "name": "Generic High Entropy Secret - commit://d83fb6af5cb9407721ea97310b877c47b7702b74/content/post/news/index.smd"}]} \ No newline at end of file diff --git a/assets/_highlight.css b/assets/_highlight.css new file mode 100644 index 0000000..b44c718 --- /dev/null +++ b/assets/_highlight.css @@ -0,0 +1,265 @@ +code .comment { + color: var(--comment-gray); +} + +code.html { +} + +code.html .string { + color: var(--dark-yellow); +} + +code.html .tag { + color: var(--blue); +} + +code.html .constant { + color: var(--light-red); +} + +code.html .attribute { + color: var(--light-yellow); +} + +code.html .punctuation { + color: var(--cyan); +} + +code.superhtml { +} + +code.superhtml .string.markup_link_url { + text-decoration: underline; + color: var(--blue); +} + +code.superhtml .string { + color: var(--dark-yellow); +} + +code.superhtml .tag { + color: var(--blue); +} + +code.superhtml .special { + color: var(--blue); +} + +code.superhtml .constant { + color: var(--light-red); +} + +code.superhtml .attribute { + color: var(--light-yellow); +} + +code.superhtml .punctuation { + color: var(--cyan); +} + +code.zig { +} + +code.zig .string { + color: var(--dark-yellow); +} + +code.zig .variable, +code.zig .field { + color: var(--light-yellow); +} + +code.zig .keyword.function { + color: var(--light-red); +} + +code.zig .bracket { + color: var(--cyan); +} + +code.zig .function { + color: var(--blue); +} + +code.zig .builtin { + color: var(--magenta); +} + +code.zig .operator, +code.zig .qualifier, +code.zig .attribute { + color: var(--light-red); +} + +code.ziggy { + color: var(--cyan); +} + +code.ziggy .keyword, +code.ziggy .type { + color: var(--light-yellow); +} + +code.ziggy .string { + color: var(--dark-yellow); +} + +code.ziggy .numeric.constant { + color: var(--magenta); +} + +code.ziggy .function { + color: var(--blue); +} + +code.ziggy-schema { + color: var(--cyan); +} + +code.ziggy-schema .keyword, +code.ziggy-schema .type { + color: var(--light-yellow); +} + +code.ziggy-schema .string { + color: var(--dark-yellow); +} + +code.ziggy-schema .numeric.constant { + color: var(--magenta); +} + +code.ziggy-schema .function { + color: var(--blue); +} + +code.markdown { + color: var(--code-fg); +} + +code.markdown .text.title { + color: var(--light-red); +} + +code.markdown .punctuation { + color: var(--light-red); +} + +code.toml { + color: var(--cyan); +} + +code.toml .property { + color: var(--light-yellow); +} + +code.toml .string { + color: var(--dark-yellow); +} + +code.toml .numeric.constant { + color: var(--magenta); +} + +code.toml .punctuation { + color: var(--blue); +} + +code.lua { +} + +code.lua .string { + color: var(--dark-yellow); +} + +code.lua .variable, +code.lua .field { + color: var(--light-yellow); +} + +code.lua .keyword.function { + color: var(--light-red); +} + +code.lua .bracket { + color: var(--cyan); +} + +code.lua .function { + color: var(--blue); +} + +code.lua .builtin { + color: var(--magenta); +} + +code.lua .operator, +code.lua .qualifier, +code.lua .attribute { + color: var(--light-red); +} + +code.bash { + display: block; /* Ensures it takes up full width for padding/margin */ + padding: 15px; + border-radius: 8px; + overflow-x: auto; /* Enable horizontal scrolling for long lines */ +} + +code.bash .function { + color: var(--blue); /* Using --blue for commands and functions */ +} + +code.bash .string { + color: var(--dark-yellow); /* Using --dark-yellow for strings */ +} + +code.bash .comment { + color: var(--comment-gray); /* Using --comment-gray for comments */ + font-style: italic; +} + +code.bash .operator, +code.bash .property { + color: var(--light-red); /* Using --light-red for operators and properties */ +} + +:root { + --light-yellow: #b58900; + --dark-yellow: #b57614; + --blue: #268bd2; + --cyan: #2aa198; + --light-red: #dc322f; + --dark-red: #cb4b16; + --comment-gray: #657b83; + --magenta: #6c71c4; +} +code.markdown { + color: #333; +} + +code, +pre { + background: var(--code-bg) !important; + color: var(--code-fg) !important; +} + +code.conf { + color: var(--cyan); +} + +code.conf .punctuation_bracket { + color: var(--blue); /* [section] */ + font-weight: bold; +} + +code.conf .function { + color: var(--light-yellow); /* key */ +} + +code.conf .comment { + color: var(--comment-gray); /* ; comment or # comment */ + font-style: italic; +} + +/* TODO: cpp support */ \ No newline at end of file diff --git a/assets/highlight.css b/assets/highlight.css index b44c718..e2c871d 100644 --- a/assets/highlight.css +++ b/assets/highlight.css @@ -1,265 +1,33 @@ -code .comment { - color: var(--comment-gray); -} - -code.html { -} - -code.html .string { - color: var(--dark-yellow); -} - -code.html .tag { - color: var(--blue); -} - -code.html .constant { - color: var(--light-red); -} - -code.html .attribute { - color: var(--light-yellow); -} - -code.html .punctuation { - color: var(--cyan); -} - -code.superhtml { -} - -code.superhtml .string.markup_link_url { - text-decoration: underline; - color: var(--blue); -} - -code.superhtml .string { - color: var(--dark-yellow); -} - -code.superhtml .tag { - color: var(--blue); -} - -code.superhtml .special { - color: var(--blue); -} - -code.superhtml .constant { - color: var(--light-red); -} - -code.superhtml .attribute { - color: var(--light-yellow); -} - -code.superhtml .punctuation { - color: var(--cyan); -} - -code.zig { -} - -code.zig .string { - color: var(--dark-yellow); -} - -code.zig .variable, -code.zig .field { - color: var(--light-yellow); -} - -code.zig .keyword.function { - color: var(--light-red); -} - -code.zig .bracket { - color: var(--cyan); -} - -code.zig .function { - color: var(--blue); -} - -code.zig .builtin { - color: var(--magenta); -} - -code.zig .operator, -code.zig .qualifier, -code.zig .attribute { - color: var(--light-red); -} - -code.ziggy { - color: var(--cyan); -} - -code.ziggy .keyword, -code.ziggy .type { - color: var(--light-yellow); -} - -code.ziggy .string { - color: var(--dark-yellow); -} - -code.ziggy .numeric.constant { - color: var(--magenta); -} - -code.ziggy .function { - color: var(--blue); -} - -code.ziggy-schema { - color: var(--cyan); -} - -code.ziggy-schema .keyword, -code.ziggy-schema .type { - color: var(--light-yellow); -} - -code.ziggy-schema .string { - color: var(--dark-yellow); -} - -code.ziggy-schema .numeric.constant { - color: var(--magenta); -} - -code.ziggy-schema .function { - color: var(--blue); -} - -code.markdown { - color: var(--code-fg); -} - -code.markdown .text.title { - color: var(--light-red); -} - -code.markdown .punctuation { - color: var(--light-red); -} - -code.toml { - color: var(--cyan); -} - -code.toml .property { - color: var(--light-yellow); -} - -code.toml .string { - color: var(--dark-yellow); -} - -code.toml .numeric.constant { - color: var(--magenta); -} - -code.toml .punctuation { - color: var(--blue); -} - -code.lua { -} - -code.lua .string { - color: var(--dark-yellow); -} - -code.lua .variable, -code.lua .field { - color: var(--light-yellow); -} - -code.lua .keyword.function { - color: var(--light-red); -} - -code.lua .bracket { - color: var(--cyan); -} - -code.lua .function { - color: var(--blue); -} - -code.lua .builtin { - color: var(--magenta); -} - -code.lua .operator, -code.lua .qualifier, -code.lua .attribute { - color: var(--light-red); -} - -code.bash { - display: block; /* Ensures it takes up full width for padding/margin */ - padding: 15px; - border-radius: 8px; - overflow-x: auto; /* Enable horizontal scrolling for long lines */ -} - -code.bash .function { - color: var(--blue); /* Using --blue for commands and functions */ -} - -code.bash .string { - color: var(--dark-yellow); /* Using --dark-yellow for strings */ -} - -code.bash .comment { - color: var(--comment-gray); /* Using --comment-gray for comments */ - font-style: italic; -} - -code.bash .operator, -code.bash .property { - color: var(--light-red); /* Using --light-red for operators and properties */ -} - -:root { - --light-yellow: #b58900; - --dark-yellow: #b57614; - --blue: #268bd2; - --cyan: #2aa198; - --light-red: #dc322f; - --dark-red: #cb4b16; - --comment-gray: #657b83; - --magenta: #6c71c4; -} -code.markdown { - color: #333; -} - -code, -pre { - background: var(--code-bg) !important; - color: var(--code-fg) !important; -} - -code.conf { - color: var(--cyan); -} - -code.conf .punctuation_bracket { - color: var(--blue); /* [section] */ - font-weight: bold; -} - -code.conf .function { - color: var(--light-yellow); /* key */ -} - -code.conf .comment { - color: var(--comment-gray); /* ; comment or # comment */ - font-style: italic; -} - -/* TODO: cpp support */ \ No newline at end of file +.zig{ + color: #BD976A; + .keyword, .keyword_modifier, .type_builtin, .keyword_type, .keyword_return, .keyword_conditional, .keyword_repeat, .keyword_operator, .constant_builtin, .keyword_exception{ + color: #4D9375; + } + .variable, .function_builtin{ + color: #BD976A; + } + .comment{ + color: #758575DD; + } + .operator, .punctuation{ + color: #666666; + } + .string, .character{ + color: #C98A7D; + } + .number{ + color: #4C9A91; + } + .keyword_function, .punctuation_delimiter{ + color: #CB7676; + } + .punctuation_bracket{ + color: #93aa9e; + } +} + +@media (prefers-color-scheme: light) { + code{ + filter: brightness(0.9) contrast(1.3); + } +} \ No newline at end of file diff --git a/assets/icons/arrow-left.svg b/assets/icons/arrow-left.svg new file mode 100644 index 0000000..333d668 --- /dev/null +++ b/assets/icons/arrow-left.svg @@ -0,0 +1,18 @@ + + + + Arrow-Left + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/arrow-right.svg b/assets/icons/arrow-right.svg new file mode 100644 index 0000000..aa292d3 --- /dev/null +++ b/assets/icons/arrow-right.svg @@ -0,0 +1,18 @@ + + + + Arrow-Right + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/style.css b/assets/style.css index 5c4aa06..37beebf 100644 --- a/assets/style.css +++ b/assets/style.css @@ -137,6 +137,15 @@ footer > div img { #prev-next a { text-decoration: none; + vertical-align: middle; +} + +#prev-next > :first-child span::before{ + content: "←"; +} + +#prev-next > :last-child span::after{ + content: "→"; } ::-webkit-scrollbar { diff --git a/content/learn/03-language-overview-1.smd b/content/learn/03-language-overview-1.smd index 8164c8e..0820688 100644 --- a/content/learn/03-language-overview-1.smd +++ b/content/learn/03-language-overview-1.smd @@ -443,7 +443,7 @@ pub fn main() void { 会输出: -``` +```zig struct{comptime year: comptime_int = 2023, comptime month: comptime_int = 8} ``` diff --git a/content/post/index.smd b/content/post/index.smd index 69b3f06..ed228fc 100644 --- a/content/post/index.smd +++ b/content/post/index.smd @@ -7,3 +7,5 @@ --- 欢迎大家向我们投稿,会同步到微信公众号,投稿方式见[这里](./2023-09-05-hello-world)。 + +[社区新闻](./news) \ No newline at end of file diff --git a/layouts/learn.shtml b/layouts/learn.shtml index aa8b4a8..2d9b891 100644 --- a/layouts/learn.shtml +++ b/layouts/learn.shtml @@ -13,14 +13,12 @@ diff --git a/main b/main index e7911e6..625331f 160000 --- a/main +++ b/main @@ -1 +1 @@ -Subproject commit e7911e6cc6408d2955bf3683a7b2c46577f6d780 +Subproject commit 625331fdccaf91c106d705d43a13418a961474c6 From 1fd825139b5dc792865fecbea5849ef5504ed8c5 Mon Sep 17 00:00:00 2001 From: xihale Date: Tue, 1 Jul 2025 18:09:29 +0800 Subject: [PATCH 04/22] Add GitHub Actions workflow for deploying the website to GitHub Pages. This includes setup for Zine, building the project, and uploading artifacts for deployment. --- .github/workflows/gh-pages.yml | 49 ++++++++++++++++++++++++++++++++++ layouts/learn.shtml | 2 -- 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/gh-pages.yml diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 0000000..12f2bcd --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,49 @@ +name: Deploy the website to Github Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Change if you need git info + + - name: Setup Zine + uses: kristoff-it/setup-zine@v1 + with: + version: v0.10.2 + + - name: Build + run: zine release + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: 'public' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/layouts/learn.shtml b/layouts/learn.shtml index 2d9b891..75a8dda 100644 --- a/layouts/learn.shtml +++ b/layouts/learn.shtml @@ -3,9 +3,7 @@ -
    -

    Table of Contents

    From 685f75136ee227227e8572af30b3345e0d8cc61c Mon Sep 17 00:00:00 2001 From: xihale Date: Tue, 1 Jul 2025 18:15:01 +0800 Subject: [PATCH 05/22] Refactor CSS for code highlighting by introducing CSS variables for colors, improving maintainability and consistency across styles. --- assets/highlight.css | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/assets/highlight.css b/assets/highlight.css index e2c871d..6225575 100644 --- a/assets/highlight.css +++ b/assets/highlight.css @@ -1,28 +1,40 @@ +:root { + --fg: #BD976A; + --keyword: #4D9375; + --variable: #BD976A; + --comment: #758575DD; + --operator: #666666; + --string: #C98A7D; + --number: #4C9A91; + --function: #CB7676; + --bracket: #93aa9e; +} + .zig{ - color: #BD976A; + color: var(--fg); .keyword, .keyword_modifier, .type_builtin, .keyword_type, .keyword_return, .keyword_conditional, .keyword_repeat, .keyword_operator, .constant_builtin, .keyword_exception{ - color: #4D9375; + color: var(--keyword); } .variable, .function_builtin{ - color: #BD976A; + color: var(--variable); } .comment{ - color: #758575DD; + color: var(--comment); } .operator, .punctuation{ - color: #666666; + color: var(--operator); } .string, .character{ - color: #C98A7D; + color: var(--string); } .number{ - color: #4C9A91; + color: var(--number); } .keyword_function, .punctuation_delimiter{ - color: #CB7676; + color: var(--function); } .punctuation_bracket{ - color: #93aa9e; + color: var(--bracket); } } From 3e8f4cb5d774d7a586ccbf8b44f80901d40cd340 Mon Sep 17 00:00:00 2001 From: xihale Date: Tue, 1 Jul 2025 18:16:50 +0800 Subject: [PATCH 06/22] Update GitHub Actions workflow to deploy to the 'zine' branch instead of 'main'. --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 12f2bcd..15bee83 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -3,7 +3,7 @@ name: Deploy the website to Github Pages on: # Runs on pushes targeting the default branch push: - branches: ["main"] + branches: ["zine"] # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: From 3fe7dbc8835031bed4a009c17d054fd005090877 Mon Sep 17 00:00:00 2001 From: xihale Date: Tue, 1 Jul 2025 18:20:11 +0800 Subject: [PATCH 07/22] Refactor CSS variable for code foreground color in highlighting styles to improve clarity and maintainability. --- assets/highlight.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/highlight.css b/assets/highlight.css index 6225575..c1ed4a8 100644 --- a/assets/highlight.css +++ b/assets/highlight.css @@ -1,5 +1,5 @@ :root { - --fg: #BD976A; + --code-fg: #BD976A; --keyword: #4D9375; --variable: #BD976A; --comment: #758575DD; @@ -11,7 +11,7 @@ } .zig{ - color: var(--fg); + color: var(--code-fg); .keyword, .keyword_modifier, .type_builtin, .keyword_type, .keyword_return, .keyword_conditional, .keyword_repeat, .keyword_operator, .constant_builtin, .keyword_exception{ color: var(--keyword); } From f468fb5aae7b3a0965467f10cf58d012b7a49ac9 Mon Sep 17 00:00:00 2001 From: xihale Date: Tue, 1 Jul 2025 18:32:47 +0800 Subject: [PATCH 08/22] Refactor CSS styles by removing unused logo width definition, adjusting body margin, and adding responsive design for max-width and display properties. Update base template to clean up commented code. --- assets/index.css | 3 --- assets/style.css | 10 +++++++++- layouts/templates/base.shtml | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/assets/index.css b/assets/index.css index e1c28f5..0450b32 100644 --- a/assets/index.css +++ b/assets/index.css @@ -2,7 +2,4 @@ font-size: 1.2rem; display: flex; flex-direction: column; -} -#logo{ - width: 40vw; } \ No newline at end of file diff --git a/assets/style.css b/assets/style.css index 37beebf..43ce134 100644 --- a/assets/style.css +++ b/assets/style.css @@ -1,4 +1,5 @@ :root { + /* TODO */ --bg: #fff; --fg: #111; --border: #aaa; @@ -27,8 +28,11 @@ } body { - margin: auto; + margin: 20px auto; max-width: 60vw; + @media screen and (max-width: 800px) { + max-width: 93vw; + } line-height: 1.5; background: var(--bg); color: var(--fg); @@ -85,6 +89,9 @@ li > code { display: inline-block; text-decoration: none; color: inherit; + @media screen and (max-width: 800px) { + display: none; + } } nav { @@ -100,6 +107,7 @@ footer > div { display: flex; flex-direction: row; justify-content: space-between; + flex-wrap: wrap; } footer > div div { diff --git a/layouts/templates/base.shtml b/layouts/templates/base.shtml index 90bc0ca..a0c8674 100644 --- a/layouts/templates/base.shtml +++ b/layouts/templates/base.shtml @@ -28,7 +28,7 @@ Source Code | BY-NC-ND 4.0 - | +

    From 10f96c04c5b7369529713e6d17281221f40e3b25 Mon Sep 17 00:00:00 2001 From: xihale Date: Tue, 1 Jul 2025 18:37:16 +0800 Subject: [PATCH 09/22] Add line-break property to anchor tags in CSS for improved text wrapping. --- assets/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/style.css b/assets/style.css index 43ce134..0d012c0 100644 --- a/assets/style.css +++ b/assets/style.css @@ -40,6 +40,7 @@ body { a { color: inherit; + line-break: anywhere; } p:has(+ pre) { From 45474c4f1b632e1fb7774050719c60166561fa2c Mon Sep 17 00:00:00 2001 From: xihale Date: Tue, 1 Jul 2025 20:26:32 +0800 Subject: [PATCH 10/22] Remove obsolete layout files and update SMD files for improved content structure and clarity. Change layout references in monthly and post templates, and update links in various sections for consistency. --- .vscode/settings.json | 6 ++ content/learn/index.smd | 6 +- content/monthly/202306.smd | 2 +- content/monthly/202308.smd | 2 +- content/monthly/202309.smd | 4 +- content/monthly/202405.smd | 10 ++-- content/monthly/202406.smd | 8 +-- content/monthly/202407.smd | 8 +-- content/monthly/202410.smd | 20 +++---- content/monthly/202411.smd | 8 +-- layouts/blog.shtml | 25 -------- layouts/community.shtml | 0 layouts/contributing.shtml | 0 layouts/learn.shtml | 11 ++-- layouts/monthly.shtml | 23 +------ layouts/page.shtml | 5 +- layouts/post.shtml | 29 +-------- layouts/templates/content.shtml | 27 +++++++++ main | 1 - remove_section_id_links.js | 102 ++++++++++++++++++++++++++++++++ 20 files changed, 175 insertions(+), 122 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 layouts/blog.shtml delete mode 100644 layouts/community.shtml delete mode 100644 layouts/contributing.shtml create mode 100644 layouts/templates/content.shtml delete mode 160000 main create mode 100644 remove_section_id_links.js diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d2acfc4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "cSpell.words": [ + "stylesheet", + "subpages" + ] +} \ No newline at end of file diff --git a/content/learn/index.smd b/content/learn/index.smd index 1ba68c8..e73fe53 100644 --- a/content/learn/index.smd +++ b/content/learn/index.smd @@ -2,11 +2,11 @@ .title = "Learning Zig 中文翻译", .date = @date("2024-01-01T00:00:00"), .author = "Karl Seguin; ZigCC", -.layout = "page.shtml", +.layout = "learn.shtml", .draft = false, --- -## [Section Contents]($section.id('table-of-contents')) +## [《学习 Zig》 目录]($section.id('table-of-contents')) - [前言](./01-preface) - [安装 Zig](./02-installing-zig) @@ -20,8 +20,6 @@ - [实战](./10-coding-in-zig) - [总结](./11-conclusion) -## 牟言 - [《学习 Zig》](https://www.openmymind.net/learning_zig/)系列教程最初由 [Karl Seguin](https://github.com/karlseguin) 编写,该教程行文流畅,讲述的脉络由浅入深,深入浅出,是入门 Zig 非常不错的选择。因此,[Zig 中文社区](https://ziglang.cc)将其翻译成中文,便于在中文用户内阅读与传播。 初次接触 Zig 的用户可以按序号依次阅读,对于有经验的 Zig 开发者可按需阅读感兴趣的章节。 diff --git a/content/monthly/202306.smd b/content/monthly/202306.smd index 876cc7b..af11442 100644 --- a/content/monthly/202306.smd +++ b/content/monthly/202306.smd @@ -86,5 +86,5 @@ $ curl http://localhost:8012/api/1/exec \ - [Rust VS Zig benchmarks](https://programming-language-benchmarks.vercel.app/rust-vs-zig) :: Which programming language or compiler is faster - [ziglang/shell](https://github.com/ziglang/shell-completions) :: Shell completions for the Zig compiler. - [menduz/zig](https://github.com/menduz/zig-steamworks) :: Steamwork bindings for Zig. -# [[Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2023-05-01..2023-06-01)]($section.id('[Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2023-05-01..2023-06-01)')) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2023-05-01..2023-06-01) - [WASI: Implement experimental threading support by Luukdegram · Pull Request #16207 · ziglang/zig](https://github.com/ziglang/zig/pull/16207) diff --git a/content/monthly/202308.smd b/content/monthly/202308.smd index e403d00..514ab6c 100644 --- a/content/monthly/202308.smd +++ b/content/monthly/202308.smd @@ -7,7 +7,7 @@ .custom = { .lastmod = "2023-09-04T22:05:21+0800" }, --- -# [[0.11 正式发布](https://ziglang.org/download/0.11.0/release-notes.html#x86-Backend)]($section.id('[0.11 正式发布](https://ziglang.org/download/0.11.0/release-notes.html#x86-Backend)')) +# [0.11 正式发布](https://ziglang.org/download/0.11.0/release-notes.html#x86-Backend) 0.11 终于在 8 月 4 号释出了,下面来看看它的一些重要改进吧。[HN 讨论](https://news.ycombinator.com/item?id=36995735) ## [Peer Type Resolution Improvements]($section.id('Peer Type Resolution Improvements')) 对等类型解析算法得到改进,下面是一些在 0.10 中不能解析,但在 0.11 中可以解析的例子: diff --git a/content/monthly/202309.smd b/content/monthly/202309.smd index 9d64b17..c81e5a6 100644 --- a/content/monthly/202309.smd +++ b/content/monthly/202309.smd @@ -8,7 +8,7 @@ --- # [重大事件]($section.id('重大事件')) -## [[Bounties Damage Open Source Projects](https://ziglang.org/news/bounties-damage-open-source-projects/)]($section.id('[Bounties Damage Open Source Projects](https://ziglang.org/news/bounties-damage-open-source-projects/)')) +## [Bounties Damage Open Source Projects](https://ziglang.org/news/bounties-damage-open-source-projects/) 在 2023-09-11 号,Wasmerio CEO 创建了 [Support WASIX · Issue #17115](https://github.com/ziglang/zig/issues/17115),表示想赞助 Zig 开发者,让其更好地支持 WASIX 平台。 Andrew 与 Loris 在这篇文章中主要阐述了这么做为什么是伤害社区的行为: @@ -17,7 +17,7 @@ Andrew 与 Loris 在这篇文章中主要阐述了这么做为什么是伤害社 这篇文章其实很符合 Andrew 的理念,不想让过多的热钱涌入 Zig 社区,他更想保证 Zig 的独立性,这也是他们创办 [Software You Can Love](https://kristoff.it/blog/the-open-source-game/) 的初衷。 -## [[Bun 1.0](https://bun.sh/blog/bun-v1.0)]($section.id('[Bun 1.0](https://bun.sh/blog/bun-v1.0)')) +## [Bun 1.0](https://bun.sh/blog/bun-v1.0) 面包终于出炉了!Bun 毫无疑问是 Zig 的明星项目,它在 2023-09-08 正式发布了 1.0 版本,这对开发者来说可能没什么太大的区别,毕竟就只是个 tag 而已,但是对于广大的用户来说,这无疑意味着可以在生产环境中去使用了。 Bun 并不简单的是个 Node.js 替代品,而是大而全的工具链: diff --git a/content/monthly/202405.smd b/content/monthly/202405.smd index 5d9adc5..790e884 100644 --- a/content/monthly/202405.smd +++ b/content/monthly/202405.smd @@ -8,9 +8,9 @@ --- # [观点/教程]($section.id('观点/教程')) -## [[Thoughts on Zig](https://arne.me/blog/thoughts-on-zig)]($section.id('[Thoughts on Zig](https://arne.me/blog/thoughts-on-zig)')) +## [Thoughts on Zig](https://arne.me/blog/thoughts-on-zig) 又一篇 Zig 初学者的使用体验文档,如果你也在犹豫要不要学 Zig,这是个不错的经验参考。 -## [[I'm sold on Zig's simplicity : r/Zig](https://www.reddit.com/r/Zig/comments/1ckstjv/im_sold_on_zigs_simplicity/)]($section.id('[I\'m sold on Zig\'s simplicity : r/Zig](https://www.reddit.com/r/Zig/comments/1ckstjv/im_sold_on_zigs_simplicity/)')) +## [I'm sold on Zig's simplicity : r/Zig](https://www.reddit.com/r/Zig/comments/1ckstjv/im_sold_on_zigs_simplicity/) 一个具有资深经验开发者,在这里描述了自己选择业余项目语言的经历: - Rust 越来越复杂,有种发展成 C++ 的趋势 - C++ 新版本的特性(比如 module)LSP 支持的不够好,而且历史包袱严重 @@ -22,13 +22,13 @@ - 与 C 无缝交换, - 具有 Result 效果的错误处理 - 唯一缺失的就是『接口』,但这一点并不是很关键,就像在 C里也没有,但是 C 可以做任何事 -# [[Zig's New CLI Progress Bar Explained](https://andrewkelley.me/post/zig-new-cli-progress-bar-explained.html)]($section.id('[Zig\'s New CLI Progress Bar Explained](https://andrewkelley.me/post/zig-new-cli-progress-bar-explained.html)')) +# [Zig's New CLI Progress Bar Explained](https://andrewkelley.me/post/zig-new-cli-progress-bar-explained.html) Andrew 的一篇文章,讲述了在最新版的 Zig 中,对进度条的改进实现,现在的进度展示更加友好。 实现的难点在于在多线程环境下,如何保证高性能,文章中大致讲述了其实现: - 首先通过预先分配好需要使用的结构,保证后续无需在进行 heap 申请 - 通过 atomic 操作来实现一个无锁的 freelist,用于申请、释放 Node -# [[Writing a task scheduler in Zig](https://www.openmymind.net/Writing-a-Task-Scheduler-in-Zig/)]($section.id('[Writing a task scheduler in Zig](https://www.openmymind.net/Writing-a-Task-Scheduler-in-Zig/)')) +# [Writing a task scheduler in Zig](https://www.openmymind.net/Writing-a-Task-Scheduler-in-Zig/) Openmymind 作者的又一力作,通过编写一个任务调度器,讲述了多线程编程的基本要领: - 共享的数据要加锁 - 条件变量要和锁一起使用,会有[虚假唤醒](https://en.wikipedia.org/wiki/Spurious_wakeup)的问题,因此在被唤醒时,需要重新检查状态是否正确。 @@ -65,4 +65,4 @@ fn wait(c: *std.Thread.Condition, mutex: *std.Thread.Mutex) void { - [deckarep/ziglang-set](https://github.com/deckarep/ziglang-set) :: A generic and general purpose Set implementation for the Zig language - [akarpovskii/tuile](https://github.com/akarpovskii/tuile) :: A Text UI library for Zig -# [[Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2024-05-01..2024-06-01)]($section.id('[Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2024-05-01..2024-06-01)')) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2024-05-01..2024-06-01) diff --git a/content/monthly/202406.smd b/content/monthly/202406.smd index fef4f7a..8cfa90b 100644 --- a/content/monthly/202406.smd +++ b/content/monthly/202406.smd @@ -24,7 +24,7 @@ const map = std.StaticStringMap(T).initComptime(kvs_list); - 将并发引入语义分析,进一步提高编译速度。 # [观点/教程]($section.id('观点/教程')) -## [[Leveraging Zig's Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/)]($section.id('[Leveraging Zig\'s Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/)')) +## [Leveraging Zig's Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/) 老朋友 openmymind 的又一篇好文章:如何利用 Zig 的 Allocator 来实现请求级别的内存分配。 // TODO Zig Allocator 的最佳应用。这里(/post/2024/06/16/leveraging-zig-allocator/)它的中文翻译。 @@ -90,7 +90,7 @@ fn run(worker: *Worker) void { } } ``` -## [[On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/)]($section.id('[On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/)')) +## [On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/) - https://news.ycombinator.com/item?id=40735667 这篇文章作者描述了所在公司在改造老 C/C++ 项目时,为什么选择了 Zig 而不是 Rust。 重写的项目运行在多个平台上(Web、移动端、VR 设备),因此最靠谱的方案就是暴露一个 C API,然后通过 FFI 来调用。在做决策时,重点关注以下两点: @@ -111,9 +111,9 @@ fn run(worker: *Worker) void { Zig 大大减少了移植现有代码库和确保所有平台兼容性所需的时间和精力。我们的团队无法相信 Rust 能让这一切变得如此简单。 相信这也是大部分人选择 Zig 的原因:简洁、高效。 -## [[Packing some Zig before going for the countryside](https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/)]($section.id('[Packing some Zig before going for the countryside](https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/)')) +## [Packing some Zig before going for the countryside](https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/) 作者列举的一些 Zig 学习资料、常用类库。该作者的另一篇文章也有不少资料:[2024 Collection of Zig resources](https://ludwigabap.bearblog.dev/2024-collection-of-zig-resources/) -## [[Why I am not yet ready to switch to Zig from Rust](https://turso.tech/blog/why-i-am-not-yet-ready-to-switch-to-zig-from-rust)]($section.id('[Why I am not yet ready to switch to Zig from Rust](https://turso.tech/blog/why-i-am-not-yet-ready-to-switch-to-zig-from-rust)')) +## [Why I am not yet ready to switch to Zig from Rust](https://turso.tech/blog/why-i-am-not-yet-ready-to-switch-to-zig-from-rust) Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较喜欢 C,但 C 不是一门安全的语言,因此通过 Rust,作者可以避免 写出 SIGSEGVS 的代码,尽管 Rust 是门复杂的语言,但是因为它有完善的生态(有大公司如微软、谷歌等做背书)、已经内存安全等特点, 已经是作者系统编程的首选。 diff --git a/content/monthly/202407.smd b/content/monthly/202407.smd index e6c6f6b..003a7c8 100644 --- a/content/monthly/202407.smd +++ b/content/monthly/202407.smd @@ -24,7 +24,7 @@ Zig 开发者的一些观点: C 和 C++ 是著名的核心编程语言,在这两种语言中,你可以完全控制硬件。 但与此同时,这些语言的工具链却非常糟糕。 Zig 允许用户涉猎这些核心编程语言,但可以使用更好的工具链,兼容各种语言和更丰富的功能。 # [观点/教程]($section.id('观点/教程')) -## [[Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/)]($section.id('[Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/)')) +## [Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/) Loris Cro 的最新文章,介绍了一个改进 Zig 编码体验的小技巧,十分推荐大家使用。具体来说是这样的: 通过配置 zls,达到保存文件时,自动进行源码检查,而且速度非常快! ```js @@ -54,14 +54,14 @@ check.dependOn(&exe_check.step); zls --config-path zls.json ``` 这样不同的项目就可以用不同的检查步骤了。 -## [[Systems Distributed '24](https://guergabo.substack.com/p/systems-distributed-24)]($section.id('[Systems Distributed \'24](https://guergabo.substack.com/p/systems-distributed-24)')) +## [Systems Distributed '24](https://guergabo.substack.com/p/systems-distributed-24) 作者对这次会议的一个回顾总结,议题主要有如下几个方向: - Systems Thinking and Engineering Culture - The Rise of New Software Abstractions - Ensuring Safe and Correct Software - Lessons from Building Distributed Databases - Notes from Water Cooler Chats -## [[C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself](https://jstrieb.github.io/posts/c-reflection-zig/)]($section.id('[C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself](https://jstrieb.github.io/posts/c-reflection-zig/)')) +## [C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself](https://jstrieb.github.io/posts/c-reflection-zig/) 该作者分享了利用 typeInfo 来在编译时获取字段名的能力,要知道,在 C 里面是没有这个功能的。 ```zig pub export fn WindowProc(hwnd: win32.HWND, uMsg: c_uint, wParam: win32.WPARAM, lParam: win32.LPARAM) callconv(windows.WINAPI) win32.LRESULT { @@ -95,7 +95,7 @@ fn get_window_messages() [65536][:0]const u8 { return result; } ``` -## [[A TypeScripter's Take on Zig (Advent of Code 2023)](https://effectivetypescript.com/2024/07/17/advent2023-zig/)]($section.id('[A TypeScripter\'s Take on Zig (Advent of Code 2023)](https://effectivetypescript.com/2024/07/17/advent2023-zig/)')) +## [A TypeScripter's Take on Zig (Advent of Code 2023)](https://effectivetypescript.com/2024/07/17/advent2023-zig/) 以下该作者的一些心得体会: - Zig 没有 scanf 等价物,正则表达式也不方便。因此,对于解析输入,它是拆分、拆分、拆分。最后,我分解出了一些 splitIntoBuf 和提取 IntsIntoBuf 帮助程序,这些帮助程序可以很快地读取大多数问题的输入。 diff --git a/content/monthly/202410.smd b/content/monthly/202410.smd index a83d747..e08587b 100644 --- a/content/monthly/202410.smd +++ b/content/monthly/202410.smd @@ -17,7 +17,7 @@ Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金 如今,我大部分的编码时间都花在了 Zig 上。 我的家人喜欢支持我们相信的事业2。 作为其中的一部分,我们希望支持那些我们认为可以带来变革和影响的独立软件项目,这既是回馈给我如此之多的社区的一种方式,更重要的是,这也是彰显和鼓励为热爱而构建的文化的一种方式。 Zig 就是这样一个项目。 # [观点/教程]($section.id('观点/教程')) -## [[Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/)]($section.id('[Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/)')) +## [Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/) 对 Zig 的特色进行了简单扼要的介绍,主要有: 1. UB 行为检测。 - Zig 的指针不能是 null,需要用 optional 类型 @@ -39,8 +39,8 @@ const instr: IType = @bitCast(encoded_instr); ``` 3. comptime,Zig 进行元编程的基础,类型是一等成员 4. 与 C 无缝交互, `zig cc` 是交叉编译的首选 -## [[Building Nintendo 3DS Homebrew with Zig](https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html)]($section.id('[Building Nintendo 3DS Homebrew with Zig](https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html)')) -## [[Critical Social Infrastructure for Zig Communities | Loris Cro's Blog](https://kristoff.it/blog/critical-social-infrastructure/)]($section.id('[Critical Social Infrastructure for Zig Communities | Loris Cro\'s Blog](https://kristoff.it/blog/critical-social-infrastructure/)')) +## [Building Nintendo 3DS Homebrew with Zig](https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html) +## [Critical Social Infrastructure for Zig Communities | Loris Cro's Blog](https://kristoff.it/blog/critical-social-infrastructure/) 对于一个试图共同学习如何制作大家都喜欢的软件的社区来说,能够分享想法并开展合作至关重要,但社交平台的不断起伏会导致连接中断,这对于一个从一开始就希望去中心化的社区来说是个大问题。 我有这种想法已经有一段时间了,但随着时间的推移,我们似乎越来越清楚地认识到,我们需要投资于能够长期保持可靠的交流形式,在这种交流形式中,变化是一种信号,表明社区正在发生转变(因此需要一种新的网络形态),而不是表明所选择的社交平台即将被收购/上市/加入人工智能大战。 @@ -48,15 +48,15 @@ const instr: IType = @bitCast(encoded_instr); 开发者日志:迈向可靠社会基础设施的第一步 - https://ziglang.org/devlog/ - https://zine-ssg.io/log/ -## [[The Zig Website Has Been Re-engineered](https://ziglang.org/news/website-zine/)]($section.id('[The Zig Website Has Been Re-engineered](https://ziglang.org/news/website-zine/)')) +## [The Zig Website Has Been Re-engineered](https://ziglang.org/news/website-zine/) Zig 官网已经用 [Zine](https://zine-ssg.io/) 重写! -## [[Rust vs. Zig in Reality: A (Somewhat) Friendly Debate](https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/)]($section.id('[Rust vs. Zig in Reality: A (Somewhat) Friendly Debate](https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/)')) -## [[Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun](https://kevinlynagh.com/rust-zig/)]($section.id('[Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun](https://kevinlynagh.com/rust-zig/)')) -## [[Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig](https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html)]($section.id('[Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig](https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html)')) +## [Rust vs. Zig in Reality: A (Somewhat) Friendly Debate](https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/) +## [Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun](https://kevinlynagh.com/rust-zig/) +## [Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig](https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html) ## [视频]($section.id('视频')) -### [[I made an operating system that self replicates doom on a network "from scratch"](https://www.youtube.com/watch?v`xOySJpQlmv4&feature`youtu.be)]($section.id('[I made an operating system that self replicates doom on a network "from scratch"](https://www.youtube.com/watch?v`xOySJpQlmv4&feature`youtu.be)')) -### [[Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature`shared&v`3fWx5BOiUiY)]($section.id('[Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature`shared&v`3fWx5BOiUiY)')) -### [[Let's explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764)]($section.id('[Let\'s explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764)')) +### [I made an operating system that self replicates doom on a network "from scratch"](https://www.youtube.com/watch?v`xOySJpQlmv4&feature`youtu.be) +### [Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature`shared&v`3fWx5BOiUiY) +### [Let's explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764) # [项目/工具]($section.id('项目/工具')) - [laohanlinux/boltdb-zig](https://github.com/laohanlinux/boltdb-zig) :: a zig implement kv database - [zigler](https://github.com/E-xyza/zigler) :: Zig NIFs in Elixir diff --git a/content/monthly/202411.smd b/content/monthly/202411.smd index 4fc63cb..4e7d706 100644 --- a/content/monthly/202411.smd +++ b/content/monthly/202411.smd @@ -8,7 +8,7 @@ --- # [观点/教程]($section.id('观点/教程')) -## [[Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html)]($section.id('[Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html)')) +## [Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html) [JAM](https://github.com/srijan-paul/jam) 作者写的一篇文章,分析里市面上现有的 JS 工具链(bundler、formatter、linter 等),虽然已经很好用,但是不够快。下面是他举的几个例子: - Lossless, cache efficient syntax trees,现在通用的 JS 语法树表示是 [ESTree](https://github.com/estree/estree),尽管设计上很简洁,但在遍历时不够高效,需要有遍历多次 才能得到有用信息(eslint 里就有四次!),而且都是指针的树结构非常不利用重复利用 CPU,Carbon 编译器就有一种更紧凑的 AST 表示。 @@ -32,7 +32,7 @@ if (matches( } ``` esquery 的问题在于执行效率,通过借助于 zig 的 comptime 来在编译器翻译 esquery 就避免了运行时开销。 -## [[Advent of Code in Zig | Loris Cro's Blog](https://kristoff.it/blog/advent-of-code-zig/)]($section.id('[Advent of Code in Zig | Loris Cro\'s Blog](https://kristoff.it/blog/advent-of-code-zig/)')) +## [Advent of Code in Zig | Loris Cro's Blog](https://kristoff.it/blog/advent-of-code-zig/) 一年一度的 AoC 又到了,这篇文章里给出了一些使用的技巧来帮助大家用 Zig 来解决 AoC 问题。 - 工具链,最新的版本 0.13 和 zls - 手册,和 [How to read the standard library source code · ziglang/zig Wiki](https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code) @@ -65,8 +65,8 @@ pub fn main() !void { 因此,请注意,虽然肯定能用 Zig 解决 AoC 问题,而且 Zig 的某些功能甚至能帮助您取得比其他语言更快的进展,但它最终还是针对软件工程进行了优化,而这并不是您在 AoC 中要做的事情。 -## [[Zig and Emulators](https://floooh.github.io/2024/08/24/zig-and-emulators.html)]($section.id('[Zig and Emulators](https://floooh.github.io/2024/08/24/zig-and-emulators.html)')) -## [[Zig Reproduced Without Binaries](https://jakstys.lt/2024/zig-reproduced-without-binaries/)]($section.id('[Zig Reproduced Without Binaries](https://jakstys.lt/2024/zig-reproduced-without-binaries/)')) +## [Zig and Emulators](https://floooh.github.io/2024/08/24/zig-and-emulators.html) +## [Zig Reproduced Without Binaries](https://jakstys.lt/2024/zig-reproduced-without-binaries/) 一个很有趣的实验,在 0.10 版本中,Zig 编译器实现了自举,即可以用老版本的 Zig 来编译 Zig 源码,生成最新的 Zig 二进制。这里重新复习一下这个复杂的流程: 之所以复杂,问题在于老版本的 Zig 从哪里来呢?对于 Zig 来说就是 [zig1.wasm](https://github.com/ziglang/zig/blob/master/stage1/zig1.wasm),它是用没自举前的 Zig,利用 LLVM 后端,以 wasm32-wasi 为目标生成的二进制文件。 diff --git a/layouts/blog.shtml b/layouts/blog.shtml deleted file mode 100644 index ff41a2f..0000000 --- a/layouts/blog.shtml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - -

    -
    -
    -

    Post list

    -
    - - -

    -
    -
    -
    - \ No newline at end of file diff --git a/layouts/community.shtml b/layouts/community.shtml deleted file mode 100644 index e69de29..0000000 diff --git a/layouts/contributing.shtml b/layouts/contributing.shtml deleted file mode 100644 index e69de29..0000000 diff --git a/layouts/learn.shtml b/layouts/learn.shtml index 75a8dda..27d672b 100644 --- a/layouts/learn.shtml +++ b/layouts/learn.shtml @@ -1,14 +1,11 @@ - + -
    -

    Table of Contents

    -
    -
    -
    -
    +

    +
    +
    diff --git a/layouts/monthly.shtml b/layouts/monthly.shtml index e44aab8..939f38b 100644 --- a/layouts/monthly.shtml +++ b/layouts/monthly.shtml @@ -1,25 +1,4 @@ - - - + -

    -

    Section Contents

    -
    - \ No newline at end of file diff --git a/layouts/page.shtml b/layouts/page.shtml index 54e79c5..939f38b 100644 --- a/layouts/page.shtml +++ b/layouts/page.shtml @@ -1,7 +1,4 @@ - - - + -

    \ No newline at end of file diff --git a/layouts/post.shtml b/layouts/post.shtml index f588f3c..939f38b 100644 --- a/layouts/post.shtml +++ b/layouts/post.shtml @@ -1,31 +1,4 @@ - - - - + -

    -
    -

    -

    -
    -
      -
    • -
    - \ No newline at end of file diff --git a/layouts/templates/content.shtml b/layouts/templates/content.shtml new file mode 100644 index 0000000..39aa6d6 --- /dev/null +++ b/layouts/templates/content.shtml @@ -0,0 +1,27 @@ + + + + + +

    +
    +

    +

    +
    +
      +
    • +
    + +
    +
    + + + +
    +
    + + + +
    +
    + \ No newline at end of file diff --git a/main b/main deleted file mode 160000 index 625331f..0000000 --- a/main +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 625331fdccaf91c106d705d43a13418a961474c6 diff --git a/remove_section_id_links.js b/remove_section_id_links.js new file mode 100644 index 0000000..0b7c9d8 --- /dev/null +++ b/remove_section_id_links.js @@ -0,0 +1,102 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// 配置 +const config = { + sourceDir: './content/monthly' +}; + +// 转换链接格式:去掉 $section.id 只保留 Markdown 链接 +function removeSectionIdLinks(content) { + return content.replace(/\[\[(.+?)\]\((.+?)\)\]\(\$section\.id\((.+?)\)\)/g, '[$1]($2)'); +} + +// 处理单个文件 +function processFile(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + const convertedContent = removeSectionIdLinks(content); + + // 如果内容有变化,写回文件 + if (content !== convertedContent) { + fs.writeFileSync(filePath, convertedContent, 'utf8'); + console.log(`✓ Removed section.id links in: ${filePath}`); + return true; + } else { + console.log(`- No changes needed: ${filePath}`); + return false; + } + } catch (error) { + console.error(`✗ Error processing ${filePath}:`, error.message); + return false; + } +} + +// 递归处理目录 +function processDirectory(dirPath) { + try { + const items = fs.readdirSync(dirPath); + let convertedCount = 0; + let totalCount = 0; + + for (const item of items) { + const fullPath = path.join(dirPath, item); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + // 递归处理子目录 + const result = processDirectory(fullPath); + convertedCount += result.converted; + totalCount += result.total; + } else if (item.endsWith('.smd')) { + // 处理 smd 文件 + const converted = processFile(fullPath); + if (converted) convertedCount++; + totalCount++; + } + } + + return { converted: convertedCount, total: totalCount }; + } catch (error) { + console.error(`✗ Error processing directory ${dirPath}:`, error.message); + return { converted: 0, total: 0 }; + } +} + +// 主函数 +function main() { + console.log('🔄 Starting section.id link removal...'); + console.log(`📁 Source directory: ${config.sourceDir}`); + console.log('🔄 Removing section.id links'); + console.log(''); + + if (!fs.existsSync(config.sourceDir)) { + console.error(`✗ Source directory does not exist: ${config.sourceDir}`); + process.exit(1); + } + + const result = processDirectory(config.sourceDir); + + console.log(''); + console.log('📊 Removal Summary:'); + console.log(`✓ Removed section.id links in: ${result.converted}/${result.total} files`); + + if (result.converted > 0) { + console.log('🎉 Section.id link removal completed!'); + } else { + console.log('ℹ️ No section.id links found'); + } +} + +// 运行脚本 +if (require.main === module) { + main(); +} + +module.exports = { + removeSectionIdLinks, + processFile, + processDirectory +}; \ No newline at end of file From f64fe57e38229d302c8128f3dc55ebf4881e03b4 Mon Sep 17 00:00:00 2001 From: xihale Date: Tue, 1 Jul 2025 21:59:43 +0800 Subject: [PATCH 11/22] Refactor markdown to SMD conversion script, changing source directory and layout configuration. Remove outdated conversion scripts and update CSS styles for improved clarity and maintainability. Update SMD files with accurate dates and links for better content structure. --- TODO.md | 3 + assets/highlight.css | 56 +- assets/style.css | 29 +- content/monthly/202207.smd | 65 ++- content/monthly/202208.smd | 127 +++-- content/monthly/202209.smd | 133 +++-- content/monthly/202210.smd | 64 ++- content/monthly/202211.smd | 101 +++- content/monthly/202212.smd | 7 +- content/monthly/202301.smd | 78 ++- content/monthly/202302.smd | 149 +++-- content/monthly/202303.smd | 168 ++++-- content/monthly/202304.smd | 125 ++++- content/monthly/202305.smd | 125 +++-- content/monthly/202306.smd | 219 ++++++-- content/monthly/202307.smd | 108 +++- content/monthly/202308.smd | 204 +++++-- content/monthly/202309.smd | 146 +++-- content/monthly/202310.smd | 84 ++- content/monthly/202311.smd | 157 ++++-- content/monthly/202402.smd | 122 ++-- content/monthly/202403.smd | 240 +++++--- content/monthly/202404.smd | 114 ++-- content/monthly/202405.smd | 111 ++-- content/monthly/202406.smd | 118 ++-- content/monthly/202407.smd | 136 +++-- content/monthly/202410.smd | 158 ++++-- content/monthly/202411.smd | 185 +++--- content/monthly/index.smd | 7 +- content/post/2023-09-05-bog-gc-1.smd | 2 + content/post/2023-09-05-hello-world.smd | 6 +- content/post/news/2023-12-11-first-meetup.smd | 8 +- convert_figure_to_image_link.js | 50 ++ convert_md_to_smd.js | 4 +- convert_monthly_md_to_smd.js | 149 ----- convert_monthly_org_to_smd.js | 108 ---- layouts/monthly.shtml | 2 + layouts/page.shtml | 2 + layouts/post.shtml | 18 + layouts/templates/content.shtml | 1 + public/about/index.html | 61 -- public/community/index.html | 61 -- public/contributing/index.html | 61 -- public/highlight.css | 37 -- public/index.css | 9 - public/index.html | 61 -- public/learn/coding-in-zig/index.html | 525 ------------------ public/learn/conclusion/index.html | 63 --- public/learn/generics/index.html | 214 ------- .../heap-memory-and-allocator/index.html | 444 --------------- public/learn/index.html | 64 --- public/learn/installing-zig/index.html | 76 --- .../learn/language-overview-part1/index.html | 266 --------- .../learn/language-overview-part2/index.html | 368 ------------ public/learn/pointers/index.html | 346 ------------ public/learn/preface/index.html | 63 --- public/learn/stack-memory/index.html | 154 ----- public/learn/style-guide/index.html | 111 ---- public/monthly/index.html | 0 public/post/first-post/fanzine.jpg | Bin 124852 -> 0 bytes public/post/first-post/index.html | 90 --- public/post/index.html | 100 ---- public/post/index.xml | 32 -- public/post/second-post/index.html | 93 ---- public/style.css | 47 -- public/zigcc.svg | 31 -- 66 files changed, 2441 insertions(+), 4625 deletions(-) create mode 100644 TODO.md create mode 100644 convert_figure_to_image_link.js delete mode 100644 convert_monthly_md_to_smd.js delete mode 100644 convert_monthly_org_to_smd.js delete mode 100644 public/about/index.html delete mode 100644 public/community/index.html delete mode 100644 public/contributing/index.html delete mode 100644 public/highlight.css delete mode 100644 public/index.css delete mode 100644 public/index.html delete mode 100644 public/learn/coding-in-zig/index.html delete mode 100644 public/learn/conclusion/index.html delete mode 100644 public/learn/generics/index.html delete mode 100644 public/learn/heap-memory-and-allocator/index.html delete mode 100644 public/learn/index.html delete mode 100644 public/learn/installing-zig/index.html delete mode 100644 public/learn/language-overview-part1/index.html delete mode 100644 public/learn/language-overview-part2/index.html delete mode 100644 public/learn/pointers/index.html delete mode 100644 public/learn/preface/index.html delete mode 100644 public/learn/stack-memory/index.html delete mode 100644 public/learn/style-guide/index.html delete mode 100644 public/monthly/index.html delete mode 100644 public/post/first-post/fanzine.jpg delete mode 100644 public/post/first-post/index.html delete mode 100644 public/post/index.html delete mode 100644 public/post/index.xml delete mode 100644 public/post/second-post/index.html delete mode 100644 public/style.css delete mode 100644 public/zigcc.svg diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..3d60897 --- /dev/null +++ b/TODO.md @@ -0,0 +1,3 @@ +## 上游 Issue 计画 + +- 标题不能包含 `\|` (无报错,但是 `BUILD ERROR`) \ No newline at end of file diff --git a/assets/highlight.css b/assets/highlight.css index c1ed4a8..e3a4697 100644 --- a/assets/highlight.css +++ b/assets/highlight.css @@ -10,7 +10,7 @@ --bracket: #93aa9e; } -.zig{ +code.zig, code.javascript{ color: var(--code-fg); .keyword, .keyword_modifier, .type_builtin, .keyword_type, .keyword_return, .keyword_conditional, .keyword_repeat, .keyword_operator, .constant_builtin, .keyword_exception{ color: var(--keyword); @@ -38,6 +38,60 @@ } } +code.conf{ + color: var(--code-fg); + .function{ + color: var(--function); + } + .punctuation_bracket{ + color: var(--bracket); + } +} + +code.diff{ + color: var(--code-fg); + .addition, .string{ + color: #4D9375; + } + .deletion, .keyword{ + color: #CB7676; + } +} + +/* TODO 分词器太烂了 */ +code.bash { + color: var(--code-fg); + .comment { + color: var(--comment); + font-style: italic; + } + span.operator { + color: var(--operator); + } + .constant { + color: var(--number); + } + .string { + color: var(--string); + } + .function { + color: var(--function); + } +} + +code.json{ + color: var(--code-fg); + .constant_builtin{ + color: var(--keyword); + } + span.string_special_key{ + color: var(--string) + } + .string{ + color: var(--function) + } +} + @media (prefers-color-scheme: light) { code{ filter: brightness(0.9) contrast(1.3); diff --git a/assets/style.css b/assets/style.css index 0d012c0..8de7816 100644 --- a/assets/style.css +++ b/assets/style.css @@ -95,6 +95,31 @@ li > code { } } +table{ + border-collapse: collapse; +} + +th, td { + border: 1px solid var(--border); + padding: 0.5rem; +} + +blockquote { + border-left: 2px solid var(--border); + padding-left: 1rem; + margin-left: 1rem; + margin-right: 1rem; +} + +img { + max-width: 100%; +} + +figcaption { + font-size: small; + text-align: center; +} + nav { display: inline-block; } @@ -142,11 +167,13 @@ footer > div img { display: flex; justify-content: space-between; align-items: center; + margin-top: 20px; + padding-top: 5px; + border-top: 2px solid var(--border); } #prev-next a { text-decoration: none; - vertical-align: middle; } #prev-next > :first-child span::before{ diff --git a/content/monthly/202207.smd b/content/monthly/202207.smd index 0c652d5..8314a95 100644 --- a/content/monthly/202207.smd +++ b/content/monthly/202207.smd @@ -1,6 +1,6 @@ --- .title = "202207 | 开刊 HelloWorld", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2022-07-30T14:08:15+0800"), .author = "ZigCC", .layout = "monthly.shtml", .draft = false, @@ -8,31 +8,54 @@ # 观点/教程 -- [Zig 初体验 - Keep Coding](https://liujiacai.net/blog/2022/07/16/zig-intro/) +- [Zig 初体验 - Keep + Coding](https://liujiacai.net/blog/2022/07/16/zig-intro/) - [undefined 值的作用](https://github.com/zigcc/forum/discussions/8) - [用 Zig 优化 yes 命令](https://github.com/zigcc/forum/discussions/4) -- [Some Thoughts on Zig — Sympolymathesy, by Chris Krycho](https://v5.chriskrycho.com/journal/some-thoughts-on-zig/) +- [Some Thoughts on Zig — Sympolymathesy, by Chris + Krycho](https://v5.chriskrycho.com/journal/some-thoughts-on-zig/) - Zig 语言最吸引人的一点:小巧 -- [ziglings-solutions 题解](https://github.com/better-zig/ziglings-solutions) -- ["these are some really impressively bad semantics to choose for your programming language" / Twitter](https://twitter.com/lexi_lambda/status/1551607005026074624) -- [How to Release your Zig Applications](https://zig.news/kristoff/how-to-release-your-zig-applications-2h90) -- [Zig files are structs - Zig NEWS ⚡](https://zig.news/gowind/zig-files-are-structs-288j) -- [Is it necessary to know C (or another systems programming language) before starting with Zig? : Zig](https://www.reddit.com/r/Zig/comments/w63x6r/is_it_necessary_to_know_c_or_another_systems/) +- [ziglings-solutions + 题解](https://github.com/better-zig/ziglings-solutions) +- [“these are some really impressively bad semantics to choose for your + programming language” / + Twitter](https://twitter.com/lexi_lambda/status/1551607005026074624) +- [How to Release your Zig + Applications](https://zig.news/kristoff/how-to-release-your-zig-applications-2h90) +- [Zig files are structs - Zig NEWS + ⚡](https://zig.news/gowind/zig-files-are-structs-288j) +- [Is it necessary to know C (or another systems programming language) + before starting with Zig? : + Zig](https://www.reddit.com/r/Zig/comments/w63x6r/is_it_necessary_to_know_c_or_another_systems/) # 项目/工具 -- [Release bun v0.1.5 · oven-sh/bun](https://github.com/oven-sh/bun/releases/tag/bun-v0.1.5) -- [zls 可以解析 build addPackage 的包](https://github.com/zigcc/forum/discussions/7) -- [natecraddock/ziglua: Zig bindings for the Lua C API](https://github.com/natecraddock/ziglua) -- [Easily create TUI programs with zig-spoon!](https://zig.news/lhp/easily-create-tui-programs-with-zig-spoon-project-demonstration-4k33) -- [Zig Support plugin for IntelliJ and CLion version 0.0.6 released](https://zig.news/marioariasc/zig-support-plugin-for-intellij-and-clion-version-006-released-o68) -- [r4gus/zbor: CBOR parser written in Zig](https://github.com/r4gus/zbor) - - [zbor - a CBOR en-/ decoder - Zig NEWS ⚡](https://zig.news/r4gus/zbor-a-cbor-en-decoder-2di0) -- [How I built zig-sqlite](https://rischmann.fr/blog/how-i-built-zig-sqlite) +- [Release bun v0.1.5 · + oven-sh/bun](https://github.com/oven-sh/bun/releases/tag/bun-v0.1.5) +- [zls 可以解析 build addPackage + 的包](https://github.com/zigcc/forum/discussions/7) +- [natecraddock/ziglua: Zig bindings for the Lua C + API](https://github.com/natecraddock/ziglua) +- [Easily create TUI programs with + zig-spoon!](https://zig.news/lhp/easily-create-tui-programs-with-zig-spoon-project-demonstration-4k33) +- [Zig Support plugin for IntelliJ and CLion version 0.0.6 + released](https://zig.news/marioariasc/zig-support-plugin-for-intellij-and-clion-version-006-released-o68) +- [r4gus/zbor: CBOR parser written in + Zig](https://github.com/r4gus/zbor) + - [zbor - a CBOR en-/ decoder - Zig NEWS + ⚡](https://zig.news/r4gus/zbor-a-cbor-en-decoder-2di0) +- [How I built + zig-sqlite](https://rischmann.fr/blog/how-i-built-zig-sqlite) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2022-07-01..2022-08-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-07-01..2022-08-01) -- [std 文档展示更全面](https://twitter.com/croloris/status/1550955321694330880) - - [New Autodocs! by kristoff-it · Pull Request #12173 · ziglang/zig](https://github.com/ziglang/zig/pull/12173) -- [SIMD size suggestions: suggestions code now compiles, added more architectures by Deecellar · Pull Request #12149 · ziglang/zig](https://github.com/ziglang/zig/pull/12149) -- [std.fmt: require specifier for unwrapping ?T and E!T by Vexu · Pull Request #12232 · ziglang/zig](https://github.com/ziglang/zig/pull/12232) +- [std + 文档展示更全面](https://twitter.com/croloris/status/1550955321694330880) + - [New Autodocs! by kristoff-it · Pull Request \#12173 · + ziglang/zig](https://github.com/ziglang/zig/pull/12173) +- [SIMD size suggestions: suggestions code now compiles, added more + architectures by Deecellar · Pull Request \#12149 · + ziglang/zig](https://github.com/ziglang/zig/pull/12149) +- [std.fmt: require specifier for unwrapping ?T and E!T by Vexu · Pull + Request \#12232 · + ziglang/zig](https://github.com/ziglang/zig/pull/12232) diff --git a/content/monthly/202208.smd b/content/monthly/202208.smd index 065b726..7d64c67 100644 --- a/content/monthly/202208.smd +++ b/content/monthly/202208.smd @@ -1,6 +1,6 @@ --- .title = "202208 | stage2 默认开启", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2022-08-28T16:03:12+0800"), .author = "ZigCC", .layout = "monthly.shtml", .draft = false, @@ -8,46 +8,99 @@ # 观点/教程 -- [Growing a {{mustache}} with Zig](https://zig.news/batiati/growing-a-mustache-with-zig-di4) +- [Growing a {{mustache}} with + Zig](https://zig.news/batiati/growing-a-mustache-with-zig-di4) 作者使用 Zig 开发时的目标: - 1. Robost,test 代码快 - 2. Optimal,Zig 语言本身就要求开发者去考虑堆分配 - 3. Reusable,用户可以在高性能(better performance)与低内存(minimal memory footprint)使用之间做取舍,以满足不同的场景 -- [Will Bun JavaScript Take Node's Crown](https://semaphoreci.com/blog/javascript-bun) - Hacker News 上的[评论](https://news.ycombinator.com/item?id=32457587)。作者从多个方面对比了 Node 与 Bun,Bun 均胜出 -- [Looking into Zig - Ayende @ Rahien](https://ayende.com/blog/194404-A/looking-into-zig)。作者对比了 Zig 与 Rust/C 的区别 -- [Cool Zig Patterns - Gotta alloc fast](https://zig.news/xq/cool-zig-patterns-gotta-alloc-fast-23h)。如果需要分配的对象只有一个,如何优化 Allocator 呢?作者给出了一个 memory pool 的实现,23 行代码 -- [Packed structs in Zig make bit/flag sets trivial | Hexops' devlog](https://devlog.hexops.com/2022/packed-structs-in-zig/)。使用 Zig 的 `packet struct` 实现 bit set 功能 + 1. Robost,test 代码快 + 2. Optimal,Zig 语言本身就要求开发者去考虑堆分配 + 3. Reusable,用户可以在高性能(better performance)与低内存(minimal + memory footprint)使用之间做取舍,以满足不同的场景 +- [Will Bun JavaScript Take Node’s + Crown](https://semaphoreci.com/blog/javascript-bun) Hacker News + 上的[评论](https://news.ycombinator.com/item?id=32457587)。作者从多个方面对比了 + Node 与 Bun,Bun 均胜出 +- [Looking into Zig - Ayende @ + Rahien](https://ayende.com/blog/194404-A/looking-into-zig)。作者对比了 + Zig 与 Rust/C 的区别 +- [Cool Zig Patterns - Gotta alloc + fast](https://zig.news/xq/cool-zig-patterns-gotta-alloc-fast-23h)。如果需要分配的对象只有一个,如何优化 + Allocator 呢?作者给出了一个 memory pool 的实现,23 行代码 +- [Packed structs in Zig make bit/flag sets trivial | Hexops’ + devlog](https://devlog.hexops.com/2022/packed-structs-in-zig/)。使用 + Zig 的 `packet struct` 实现 bit set 功能 # 项目/工具 -- [Virtual tables by vrischmann · Pull Request #100 · vrischmann/zig-sqlite](https://github.com/vrischmann/zig-sqlite/pull/100)。zig-sqlite 作者正常尝试封装 virtual table 的 API,这样就可以用 Zig 写 SQLite 模块了。 -- [Zig Support plugin for IntelliJ and CLion version 0.1.0 released](https://zig.news/marioariasc/zig-support-plugin-for-intellij-and-clion-version-010-released-pd0) -- [How to walk directories? - Help - Ziggit](https://ziggit.dev/t/how-to-walk-directories/260) -- [Zig Visual Programming with Blockly](https://zig.news/lupyuen/zig-visual-programming-with-blockly-3pbg) +- [Virtual tables by vrischmann · Pull Request \#100 · + vrischmann/zig-sqlite](https://github.com/vrischmann/zig-sqlite/pull/100)。zig-sqlite + 作者正常尝试封装 virtual table 的 API,这样就可以用 Zig 写 SQLite + 模块了。 + +- [Zig Support plugin for IntelliJ and CLion version 0.1.0 + released](https://zig.news/marioariasc/zig-support-plugin-for-intellij-and-clion-version-010-released-pd0) + +- [How to walk directories? - Help - + Ziggit](https://ziggit.dev/t/how-to-walk-directories/260) + +- [Zig Visual Programming with + Blockly](https://zig.news/lupyuen/zig-visual-programming-with-blockly-3pbg) ![](/images/blockly.webp) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2022-08-01..2022-09-01) - -- [make self-hosted the default compiler by andrewrk · Pull Request #12368](https://github.com/ziglang/zig/pull/12368) - Master 分支默认已经是 stage2,这是个里程碑的事情,作者为了用户平滑升级,还写了份[升级指南](https://github.com/ziglang/zig/wiki/Self-Hosted-Compiler-Upgrade-Guide)。优势就是:速度更快、内存占用更少、错误信息更加友好。其他相关改动: - - [stage2+stage1: remove type parameter from bit builtins by Vexu · Pull Request #12574 · ziglang/zig](https://github.com/ziglang/zig/pull/12574/files) - - [Adds std.meta.FnPtr for easier stage1/stage2 compatibility by MasterQ32 · Pull Request #12613 · ziglang/zig](https://github.com/ziglang/zig/pull/12613/files) - - [Stage2 fixes by Vexu · Pull Request #12563 · ziglang/zig](https://github.com/ziglang/zig/pull/12563/files) - - [stage2: add note about function call being comptime because of comptime only return type by Vexu · Pull Request #12499 · ziglang/zig](https://github.com/ziglang/zig/pull/12499/files) - - [stage2: implement stack protectors by andrewrk · Pull Request #12472 · ziglang/zig](https://github.com/ziglang/zig/pull/12472) - - [Stage2 error set safety improvements by Vexu · Pull Request #12416 · ziglang/zig](https://github.com/ziglang/zig/pull/12416/files) - - [stage2 llvm: implement more C ABI by Vexu · Pull Request #12395 · ziglang/zig](https://github.com/ziglang/zig/pull/12395/files) -- [coff: improve default COFF/PE object parser by kubkon · Pull Request #12575 · ziglang/zig](https://github.com/ziglang/zig/pull/12575) -- [autodoc: error sets now display all their members by der-teufel-programming · Pull Request #12583 · ziglang/zig](https://github.com/ziglang/zig/pull/12583) -- [Autodoc: anon_init_struct support by kristoff-it · Pull Request #12598 · ziglang/zig](https://github.com/ziglang/zig/pull/12598/files) -- [compilation: avoid pointless caching by kristoff-it · Pull Request #12605 · ziglang/zig](https://github.com/ziglang/zig/pull/12605) -- [translate-c: Don't add self-defined macros to global name table by ehaas · Pull Request #12562 · ziglang/zig](https://github.com/ziglang/zig/pull/12562/files) -- [fix(translate-c): fix off-by-one for leading zeroes by r00ster91 · Pull Request #12490 · ziglang/zig](https://github.com/ziglang/zig/pull/12490/files) -- [libstd: fix off-by-one error in def of ProcSym in pdb.zig by kubkon · Pull Request #12464 · ziglang/zig](https://github.com/ziglang/zig/pull/12464/files) -- [fix memory leak in NativePaths.zig by Techatrix · Pull Request #12469 · ziglang/zig](https://github.com/ziglang/zig/pull/12469/files) -- [std.fs: Fix `WalkerEntry.dir` not always being the containing dir by squeek502 · Pull Request #12444 · ziglang/zig](https://github.com/ziglang/zig/pull/12444/files) -- [std.mem.zeroes: Zero sized structs with uninitialized members by N00byEdge · Pull Request #12246 · ziglang/zig](https://github.com/ziglang/zig/pull/12246/files) -- [stage2: Implement explicit backing integers for packed structs by ifreund · Pull Request #12379 · ziglang/zig](https://github.com/ziglang/zig/pull/12379) -- [std.io.Reader: bounded array functions by InKryption · Pull Request #12351 · ziglang/zig](https://github.com/ziglang/zig/pull/12351/files) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-08-01..2022-09-01) + +- [make self-hosted the default compiler by andrewrk · Pull Request + \#12368](https://github.com/ziglang/zig/pull/12368) Master + 分支默认已经是 + stage2,这是个里程碑的事情,作者为了用户平滑升级,还写了份[升级指南](https://github.com/ziglang/zig/wiki/Self-Hosted-Compiler-Upgrade-Guide)。优势就是:速度更快、内存占用更少、错误信息更加友好。其他相关改动: + - [stage2+stage1: remove type parameter from bit builtins by Vexu · + Pull Request \#12574 · + ziglang/zig](https://github.com/ziglang/zig/pull/12574/files) + - [Adds std.meta.FnPtr for easier stage1/stage2 compatibility by + MasterQ32 · Pull Request \#12613 · + ziglang/zig](https://github.com/ziglang/zig/pull/12613/files) + - [Stage2 fixes by Vexu · Pull Request \#12563 · + ziglang/zig](https://github.com/ziglang/zig/pull/12563/files) + - [stage2: add note about function call being comptime because of + comptime only return type by Vexu · Pull Request \#12499 · + ziglang/zig](https://github.com/ziglang/zig/pull/12499/files) + - [stage2: implement stack protectors by andrewrk · Pull Request + \#12472 · ziglang/zig](https://github.com/ziglang/zig/pull/12472) + - [Stage2 error set safety improvements by Vexu · Pull Request \#12416 + · ziglang/zig](https://github.com/ziglang/zig/pull/12416/files) + - [stage2 llvm: implement more C ABI by Vexu · Pull Request \#12395 · + ziglang/zig](https://github.com/ziglang/zig/pull/12395/files) +- [coff: improve default COFF/PE object parser by kubkon · Pull Request + \#12575 · ziglang/zig](https://github.com/ziglang/zig/pull/12575) +- [autodoc: error sets now display all their members by + der-teufel-programming · Pull Request \#12583 · + ziglang/zig](https://github.com/ziglang/zig/pull/12583) +- [Autodoc: anon_init_struct support by kristoff-it · Pull Request + \#12598 · + ziglang/zig](https://github.com/ziglang/zig/pull/12598/files) +- [compilation: avoid pointless caching by kristoff-it · Pull Request + \#12605 · ziglang/zig](https://github.com/ziglang/zig/pull/12605) +- [translate-c: Don’t add self-defined macros to global name table by + ehaas · Pull Request \#12562 · + ziglang/zig](https://github.com/ziglang/zig/pull/12562/files) +- [fix(translate-c): fix off-by-one for leading zeroes by r00ster91 · + Pull Request \#12490 · + ziglang/zig](https://github.com/ziglang/zig/pull/12490/files) +- [libstd: fix off-by-one error in def of ProcSym in pdb.zig by kubkon · + Pull Request \#12464 · + ziglang/zig](https://github.com/ziglang/zig/pull/12464/files) +- [fix memory leak in NativePaths.zig by Techatrix · Pull Request + \#12469 · + ziglang/zig](https://github.com/ziglang/zig/pull/12469/files) +- [std.fs: Fix `WalkerEntry.dir` not always being the containing dir by + squeek502 · Pull Request \#12444 · + ziglang/zig](https://github.com/ziglang/zig/pull/12444/files) +- [std.mem.zeroes: Zero sized structs with uninitialized members by + N00byEdge · Pull Request \#12246 · + ziglang/zig](https://github.com/ziglang/zig/pull/12246/files) +- [stage2: Implement explicit backing integers for packed structs by + ifreund · Pull Request \#12379 · + ziglang/zig](https://github.com/ziglang/zig/pull/12379) +- [std.io.Reader: bounded array functions by InKryption · Pull Request + \#12351 · + ziglang/zig](https://github.com/ziglang/zig/pull/12351/files) diff --git a/content/monthly/202209.smd b/content/monthly/202209.smd index 2e006ce..c72c163 100644 --- a/content/monthly/202209.smd +++ b/content/monthly/202209.smd @@ -1,6 +1,6 @@ --- .title = "202209 | 锋芒毕露", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2022-10-03T23:32:12+0800"), .author = "ZigCC", .layout = "monthly.shtml", .draft = false, @@ -8,63 +8,122 @@ # Zig VS Rust 火花 -在 9/10 号左右,在 Twitter 上牵起了一小波关于 Zig VS Rust 的小火花,以至于最后 Zig 创始人 Andrew Kelley [发推](https://twitter.com/andy_kelley/status/1568679389113757698)表示 Let us exist。这里稍微整理下这件事情的过程: -本次事件主要涉及两个人: +在 9/10 号左右,在 Twitter 上牵起了一小波关于 Zig VS Rust +的小火花,以至于最后 Zig 创始人 Andrew Kelley +[发推](https://twitter.com/andy_kelley/status/1568679389113757698)表示 +Let us exist。这里稍微整理下这件事情的过程: 本次事件主要涉及两个人: - Rust 核心贡献者: Patrick Walton - Zig 社区 VP: Loris Cro ### 时间线 -- 8/26 号,一篇关于 wasm 2 Game Jam 的[分析报告](https://wasm4.org/blog/jam-2-results/)中,使用 Zig 的人数最多 -- 9/9 号,这篇报告在 HackerNews 上引起了[热烈讨论](https://news.ycombinator.com/item?id`32777636),其中 Walton 在多处回复中表示 Zig 语言的劣势,[并称](https://news.ycombinator.com/item?id`32782842) - > It's perfectly reasonable to take the position that it's deeply problematic for a language aiming for wide use in 2022 to not be memory safe. There's no requirement that you "focus on tradeoffs", especially since real people get hurt by memory safety problems. -- Loris [回复到](https://news.ycombinator.com/item?id=32785380): - > I think you're actively hurting the project that you care about in your ineffective crusade, but hey, don't let me stop you. -- 9/10 号,有人发推对 [tigerbeetle](https://github.com/tigerbeetledb/tigerbeetle/blob/main/docs/TIGER_STYLE.md#safety) 内存分配方式表示好奇:所有内存必须在启动时静态分配好 -- Walton [回复到](https://mobile.twitter.com/pcwalton/status/1568498326496247809): - > The weird thing is that this is used as an example of why you supposedly don't need language-enforced memory safety. - > - > But that literally is language-enforced memory safety! Just _way_ more restrictive than what Rust has: if you hate the borrow check, try the "no heap" check... - > - > This is wrong because you can still have UAF from freed stack frames. -- Loris 针对 Walton 的回复说了句“What a boring, useless take.([原推](https://mobile.twitter.com/kripken/status/1568428308131622913)的回复已经被 Loris 删除了,可以[在这里](https://archive.ph/jq3kw#selection-1275.0-1275.30)看到历史): -- Walton 发推[表示](https://mobile.twitter.com/pcwalton/status/1568302065851707392)在 2022 年,所有语言都应该是内存安全,应该算是『编程语言界的共识』,并称 Zig 是行业的一大退步 😅 -- Loris 专门发了一个 [Twitter thread](https://twitter.com/croloris/status/1568573729940164608?s`21&t`v2Dj_F2f_kUzZDQps5KjtQ) 来阐述『软件的目标不仅仅是内存安全,更重要的是正确』。比如 tigerbeetle 这里[提到的](https://github.com/tigerbeetledb/tigerbeetle/blob/main/docs/DESIGN.md)。而且即便内存安全,也可能发生 OOM +- 8/26 号,一篇关于 wasm 2 Game Jam + 的[分析报告](https://wasm4.org/blog/jam-2-results/)中,使用 Zig + 的人数最多 +- 9/9 号,这篇报告在 HackerNews + 上引起了[热烈讨论](https://news.ycombinator.com/item?id=32777636),其中 + Walton 在多处回复中表示 Zig + 语言的劣势,[并称](https://news.ycombinator.com/item?id=32782842) \> + It’s perfectly reasonable to take the position that it’s deeply + problematic for a language aiming for wide use in 2022 to not be + memory safe. There’s no requirement that you “focus on tradeoffs”, + especially since real people get hurt by memory safety problems. +- Loris [回复到](https://news.ycombinator.com/item?id=32785380): \> I + think you’re actively hurting the project that you care about in your + ineffective crusade, but hey, don’t let me stop you. +- 9/10 号,有人发推对 + [tigerbeetle](https://github.com/tigerbeetledb/tigerbeetle/blob/main/docs/TIGER_STYLE.md#safety) + 内存分配方式表示好奇:所有内存必须在启动时静态分配好 +- Walton + [回复到](https://mobile.twitter.com/pcwalton/status/1568498326496247809): + \> The weird thing is that this is used as an example of why you + supposedly don’t need language-enforced memory safety. \> \> But that + literally is language-enforced memory safety! Just *way* more + restrictive than what Rust has: if you hate the borrow check, try the + “no heap” check… \> \> This is wrong because you can still have UAF + from freed stack frames. +- Loris 针对 Walton 的回复说了句“What a boring, useless + take.([原推](https://mobile.twitter.com/kripken/status/1568428308131622913)的回复已经被 + Loris + 删除了,可以[在这里](https://archive.ph/jq3kw#selection-1275.0-1275.30)看到历史): +- Walton + 发推[表示](https://mobile.twitter.com/pcwalton/status/1568302065851707392)在 + 2022 年,所有语言都应该是内存安全,应该算是『编程语言界的共识』,并称 + Zig 是行业的一大退步 😅 +- Loris 专门发了一个 [Twitter + thread](https://twitter.com/croloris/status/1568573729940164608?s=21&t=v2Dj_F2f_kUzZDQps5KjtQ) + 来阐述『软件的目标不仅仅是内存安全,更重要的是正确』。比如 tigerbeetle + 这里[提到的](https://github.com/tigerbeetledb/tigerbeetle/blob/main/docs/DESIGN.md)。而且即便内存安全,也可能发生 + OOM ### 总结 上面的链接比较多,这里稍微总结下这次争论的问题: -> Rust 用户觉得 Zig 不是内存安全的语言,Zig 认为 Rust 的语言过度复杂,这反而会导致程序复杂度挺升,导致程序产生错误行为 +> Rust 用户觉得 Zig 不是内存安全的语言,Zig 认为 Rust +> 的语言过度复杂,这反而会导致程序复杂度挺升,导致程序产生错误行为 -使用 Zig 的人大概率也是 Rust 用户,之所以有了安全的 Rust,还来选 Zig,笔者觉得大概率就是本次争论的观点,Rust 过于复杂,导致程序员不仅仅要考虑业务行为,还需要按照 Rust 的风格来编程,这加剧了程序出错的可能性。 +使用 Zig 的人大概率也是 Rust 用户,之所以有了安全的 Rust,还来选 +Zig,笔者觉得大概率就是本次争论的观点,Rust +过于复杂,导致程序员不仅仅要考虑业务行为,还需要按照 Rust +的风格来编程,这加剧了程序出错的可能性。 # 观点/教程 -- [How (memory) safe is zig?](https://www.scattered-thoughts.net/writing/how-safe-is-zig/) 作者重点介绍了在内存安全方面,Zig 的优劣势。尽管 Zig 相比 C 有了明显的改进,但相比 Rust 这种在编译期就能发现程序问题来说,显得有些鸡肋,但在一些可控的环境下,比如 WASM、嵌入式环境中,Zig 还是有发挥的空间 - - [How (memory) safe is zig? (UPDATED) | Lobsters](https://lobste.rs/s/nw7hsd) - - [How safe is Zig? | Hacker News](https://news.ycombinator.com/item?id=31850347) -- [Hacked std.PriorityQueue for constant time update](https://www.reddit.com/r/Zig/comments/x8fxqr/hacked_stdpriorityqueue_for_constant_time_update/) -- [Flutter/Dart + FFI + Zig: Flutter 使用 FFI 调用 Zig 示例](https://github.com/zigcc/forum/discussions/21) -- [Building a Tiny Mutex](https://zig.news/kprotty/building-a-tiny-mutex-537k) -- [Perfecting WebGPU/Dawn native graphics for Zig](https://devlog.hexops.com/2022/perfecting-webgpu-native/) -- [Cross-Compiling and packaging C, Go and Zig projects with Nix](https://flyx.org/cross-packaging/) 介绍如何基于 [Nix](https://nixos.org/) 来进行交叉编译 -- [Revisiting the design approach to the Zig programming language](https://about.sourcegraph.com/blog/zig-programming-language-revisiting-design-approach) Sourcegraph 的一档播客,对 Zig 创始人的采访,介绍了 Zig 的由来,其中提到一个性能优化点是:untagged union,这里有它的一些介绍:[Andrew Kelley claims Zig is faster than Rust in perfomance](https://www.reddit.com/r/rust/comments/s5caye/comment/hsz6uf0/?utm_source`share&utm_medium`web2x&context=3) -- [Zig ⚡ Improving the User Experience for Unused Variables](https://vimeo.com/748218307) +- [How (memory) safe is + zig?](https://www.scattered-thoughts.net/writing/how-safe-is-zig/) + 作者重点介绍了在内存安全方面,Zig 的优劣势。尽管 Zig 相比 C + 有了明显的改进,但相比 Rust + 这种在编译期就能发现程序问题来说,显得有些鸡肋,但在一些可控的环境下,比如 + WASM、嵌入式环境中,Zig 还是有发挥的空间 + - [How (memory) safe is zig? (UPDATED) | + Lobsters](https://lobste.rs/s/nw7hsd) + - [How safe is Zig? | Hacker + News](https://news.ycombinator.com/item?id=31850347) +- [Hacked std.PriorityQueue for constant time + update](https://www.reddit.com/r/Zig/comments/x8fxqr/hacked_stdpriorityqueue_for_constant_time_update/) +- [Flutter/Dart + FFI + Zig: Flutter 使用 FFI 调用 Zig + 示例](https://github.com/zigcc/forum/discussions/21) +- [Building a Tiny + Mutex](https://zig.news/kprotty/building-a-tiny-mutex-537k) +- [Perfecting WebGPU/Dawn native graphics for + Zig](https://devlog.hexops.com/2022/perfecting-webgpu-native/) +- [Cross-Compiling and packaging C, Go and Zig projects with + Nix](https://flyx.org/cross-packaging/) 介绍如何基于 + [Nix](https://nixos.org/) 来进行交叉编译 +- [Revisiting the design approach to the Zig programming + language](https://about.sourcegraph.com/blog/zig-programming-language-revisiting-design-approach) + Sourcegraph 的一档播客,对 Zig 创始人的采访,介绍了 Zig + 的由来,其中提到一个性能优化点是:untagged + union,这里有它的一些介绍:[Andrew Kelley claims Zig is faster than + Rust in + perfomance](https://www.reddit.com/r/rust/comments/s5caye/comment/hsz6uf0/?utm_source=share&utm_medium=web2x&context=3) +- [Zig ⚡ Improving the User Experience for Unused + Variables](https://vimeo.com/748218307) # 项目/工具 -- [Zig 开发常用类库](https://github.com/zigcc/forum/discussions/28),如果读者的 Zig 项目托管在 GitHub,推荐加上 [zig-package](https://github.com/topics/zig-package) 这个标签,这样可以自动被 https://zig.pm/ 收录 +- [Zig + 开发常用类库](https://github.com/zigcc/forum/discussions/28),如果读者的 + Zig 项目托管在 GitHub,推荐加上 + [zig-package](https://github.com/topics/zig-package) + 这个标签,这样可以自动被 https://zig.pm/ 收录 - https://zig.run/ - 在线运行 zig 代码 -- [Zig Support plugin for IntelliJ and CLion version 0.0.7 released](https://zig.news/marioariasc/zig-support-plugin-for-intellij-and-clion-version-007-released-1en8) -- [zig-napigen](https://github.com/cztomsik/zig-napigen) Automatic N-API bindings for your Zig project. -- [Dart 通过 FFI 调用 Zig 库示例](https://github.com/better-dart/learn-dart/blob/main/packages/ffi-binding/example/main.dart#L31) +- [Zig Support plugin for IntelliJ and CLion version 0.0.7 + released](https://zig.news/marioariasc/zig-support-plugin-for-intellij-and-clion-version-007-released-1en8) +- [zig-napigen](https://github.com/cztomsik/zig-napigen) Automatic N-API + bindings for your Zig project. +- [Dart 通过 FFI 调用 Zig + 库示例](https://github.com/better-dart/learn-dart/blob/main/packages/ffi-binding/example/main.dart#L31) - [zig-gamedev](https://github.com/michal-z/zig-gamedev) - [loc in Zig](https://github.com/jiacai2050/loc) 代码行数统计工具 - [Grep in Zig](https://github.com/EclesioMeloJunior/zig-grep) -- [randomutils](https://gitlab.com/hdante/randomutils) Generate 64-bit random numbers -- [zig-pico](https://github.com/paperdev-code/zig-pico) 树莓派 [Pico SDK](https://github.com/raspberrypi/pico-sdk) 的 Zig 绑定库 -- [stm32f4.zig](https://github.com/moonxraccoon/stm32f4.zig) STM32F4(ARM Cortex M4 的高性能 32 位微控制器) 固件抽象层 +- [randomutils](https://gitlab.com/hdante/randomutils) Generate 64-bit + random numbers +- [zig-pico](https://github.com/paperdev-code/zig-pico) 树莓派 [Pico + SDK](https://github.com/raspberrypi/pico-sdk) 的 Zig 绑定库 +- [stm32f4.zig](https://github.com/moonxraccoon/stm32f4.zig) STM32F4(ARM + Cortex M4 的高性能 32 位微控制器) 固件抽象层 # [Zig 语言更新](https://github.com/ziglang/zig/pulls?q=+is%3Aclosed+is%3Apr+closed%3A2022-09-01..2022-10-01+) diff --git a/content/monthly/202210.smd b/content/monthly/202210.smd index ba309a4..6e0b0dc 100644 --- a/content/monthly/202210.smd +++ b/content/monthly/202210.smd @@ -1,6 +1,6 @@ --- .title = "202210 | 0.10 蓄势待发", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2022-10-30T10:10:14+0800"), .author = "ZigCC", .layout = "monthly.shtml", .draft = false, @@ -8,25 +8,51 @@ # 观点/教程 -- [Zig Is Self-Hosted Now, What's Next? | Loris Cro's Blog](https://kristoff.it/blog/zig-self-hosted-now-what/) | 0.10 即将在 11-01 号发布,Loris 回顾了目前 stage2 的进展,包管理器是下一个目标,但是不会引入中央仓库 - - Loris 会在 Twitch 直播这次的 [release party](https://ziggit.dev/t/release-party-and-zig-rush-november-1st/435) -- [Howto Pair Strings with Enums](https://zig.news/david_vanderson/howto-pair-strings-with-enums-9ce) 利用 Zig comptime 的能力为 enum 增加描述信息 -- [A Database Without Dynamic Memory Allocation](https://tigerbeetle.com/blog/a-database-without-dynamic-memory/) | TigerBeetle 讲述了如何静态内存的优势,如何应用到数据库中,以及 Zig 为实现这种设计方式的优势 -- [That Time I Tried Porting Zig to SerenityOS - sin-ack's writings](https://sin-ack.github.io/posts/sycl-talk-20221007/) -- [Building a high-performance database buffer pool in Zig using io_uring's new fixed-buffer mode](https://gavinray97.github.io/blog/io-uring-fixed-bufferpool-zig) -- [Zig-style generics are not well-suited for most languages](https://typesanitizer.com/blog/zig-generics.html) -- [Zig and WebAssembly are a match made in heaven](https://blog.battlefy.com/zig-and-webassembly-are-a-match-made-in-heaven) -- [视频] [Stay Together For The Kids - Andrew Kelley - Software You Can Love 2022](https://www.bilibili.com/video/BV1ne411G74c/?share_source`copy_web&vd_source`9191359325bcbfb53bd116d1f5b22175) -- [视频] [Ziglibc: Sweeping out the rug from underneath C - Jonathan Marler - Software You Can Love 2022](https://www.bilibili.com/video/BV1Td4y1y7U9/?share_source`copy_web&vd_source`9191359325bcbfb53bd116d1f5b22175) -- [音频] [005. 与 LemonHX 畅聊新一代编程语言 Zig – RustTalk](https://rusttalk.github.io/podcast/005/) +- [Zig Is Self-Hosted Now, What’s Next? | Loris Cro’s + Blog](https://kristoff.it/blog/zig-self-hosted-now-what/) | 0.10 + 即将在 11-01 号发布,Loris 回顾了目前 stage2 + 的进展,包管理器是下一个目标,但是不会引入中央仓库 + - Loris 会在 Twitch 直播这次的 [release + party](https://ziggit.dev/t/release-party-and-zig-rush-november-1st/435) +- [Howto Pair Strings with + Enums](https://zig.news/david_vanderson/howto-pair-strings-with-enums-9ce) + 利用 Zig comptime 的能力为 enum 增加描述信息 +- [A Database Without Dynamic Memory + Allocation](https://tigerbeetle.com/blog/a-database-without-dynamic-memory/) + | TigerBeetle 讲述了如何静态内存的优势,如何应用到数据库中,以及 Zig + 为实现这种设计方式的优势 +- [That Time I Tried Porting Zig to SerenityOS - sin-ack’s + writings](https://sin-ack.github.io/posts/sycl-talk-20221007/) +- [Building a high-performance database buffer pool in Zig using + io_uring’s new fixed-buffer + mode](https://gavinray97.github.io/blog/io-uring-fixed-bufferpool-zig) +- [Zig-style generics are not well-suited for most + languages](https://typesanitizer.com/blog/zig-generics.html) +- [Zig and WebAssembly are a match made in + heaven](https://blog.battlefy.com/zig-and-webassembly-are-a-match-made-in-heaven) +- \[视频\] [Stay Together For The Kids - Andrew Kelley - Software You + Can Love + 2022](https://www.bilibili.com/video/BV1ne411G74c/?share_source=copy_web&vd_source=9191359325bcbfb53bd116d1f5b22175) +- \[视频\] [Ziglibc: Sweeping out the rug from underneath C - Jonathan + Marler - Software You Can Love + 2022](https://www.bilibili.com/video/BV1Td4y1y7U9/?share_source=copy_web&vd_source=9191359325bcbfb53bd116d1f5b22175) +- \[音频\] [005. 与 LemonHX 畅聊新一代编程语言 Zig – + RustTalk](https://rusttalk.github.io/podcast/005/) # 项目/工具 -- [Himujjal/zig-json5](https://github.com/Himujjal/zig-json5): A JSON5 parser/stringifier for Zig resembling the std.json API -- [hexops/mach-examples](https://github.com/hexops/mach-examples): Mach core & engine examples -- [sagemathinc/cowasm](https://github.com/sagemathinc/cowasm): CoWasm: Collaborative WebAssembly for Servers and Browsers. Built using Zig. Supports Python with extension modules, including numpy. -- [loc 使用 mmap 后速度快了 2 倍](https://github.com/jiacai2050/loc/pull/2) -- 一个 Zig Hackers 的 Twitter 列表:[Zig Hero](https://twitter.com/i/lists/1570249876155568129) -- [A minimal RocksDB example with Zig](https://notes.eatonphil.com/zigrocks.html) +- [Himujjal/zig-json5](https://github.com/Himujjal/zig-json5): A JSON5 + parser/stringifier for Zig resembling the std.json API +- [hexops/mach-examples](https://github.com/hexops/mach-examples): Mach + core & engine examples +- [sagemathinc/cowasm](https://github.com/sagemathinc/cowasm): CoWasm: + Collaborative WebAssembly for Servers and Browsers. Built using Zig. + Supports Python with extension modules, including numpy. +- [loc 使用 mmap 后速度快了 2 + 倍](https://github.com/jiacai2050/loc/pull/2) +- 一个 Zig Hackers 的 Twitter 列表:[Zig + Hero](https://twitter.com/i/lists/1570249876155568129) +- [A minimal RocksDB example with + Zig](https://notes.eatonphil.com/zigrocks.html) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2022-10-01..2022-11-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-10-01..2022-11-01) diff --git a/content/monthly/202211.smd b/content/monthly/202211.smd index de49e41..e9f68be 100644 --- a/content/monthly/202211.smd +++ b/content/monthly/202211.smd @@ -1,6 +1,6 @@ --- .title = "202211 | 0.10 横空出世", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2022-12-04T18:45:34+0800"), .author = "ZigCC", .layout = "monthly.shtml", .draft = false, @@ -8,7 +8,9 @@ # [0.10.0 Release Notes](https://ziglang.org/download/0.10.0/release-notes.html) -本月最大的事情就是 0.10 版本发布了,主要功能就是 self-hosted compiler,也称为『自举』,即可以用 Zig 来写 Zig 编译器,自举之所以对于一门语言如此重要,主要在于,这说明了该语言可以处理足够复杂的系统,不再只是玩具而已。编译的提升: +本月最大的事情就是 0.10 版本发布了,主要功能就是 self-hosted +compiler,也称为『自举』,即可以用 Zig 来写 Zig +编译器,自举之所以对于一门语言如此重要,主要在于,这说明了该语言可以处理足够复杂的系统,不再只是玩具而已。编译的提升: - Wall Clock Time: 43 seconds to 40 seconds (7% faster) - Peak RSS: 9.6 GiB to 2.8 GiB (3.5x less memory used) @@ -17,16 +19,20 @@ # zigcc 中文社区微信群 -欢迎喜欢 Zig 的小伙伴加入! -{{< figure src="https://github.com/zigcc/.github/raw/main/weixin.jpg" - width`"200" title`"ZigCC 微信群二维码" >}} +欢迎喜欢 Zig 的小伙伴加入! {{\< figure +src=“https://github.com/zigcc/.github/raw/main/weixin.jpg” width=“200” +title=“ZigCC 微信群二维码” \>}} # 观点/教程 -- [Wasmer 3.0 使用 Zig 进行跨平台编译 · Discussion #35 · zigcc/forum](https://github.com/zigcc/forum/discussions/35) -- [Easy Interfaces with Zig 0.10.0](https://zig.news/kristoff/easy-interfaces-with-zig-0100-2hc5)。由于 Zig 中没有 interface/trait 这种抽象类型,作者这里通过 0.10 提供的一个新功能(inline else)来实现类似效果: +- [Wasmer 3.0 使用 Zig 进行跨平台编译 · Discussion \#35 · + zigcc/forum](https://github.com/zigcc/forum/discussions/35) +- [Easy Interfaces with Zig + 0.10.0](https://zig.news/kristoff/easy-interfaces-with-zig-0100-2hc5)。由于 + Zig 中没有 interface/trait 这种抽象类型,作者这里通过 0.10 + 提供的一个新功能(inline else)来实现类似效果: -```zig +``` zig const Animal = union(enum){ cat: Cat, dog: Dog, @@ -41,24 +47,71 @@ const Animal = union(enum){ }; ``` -- [My hopes and dreams for 'zig test'](https://zig.news/slimsag/my-hopes-and-dreams-for-zig-test-2pkh)。作者对 `zig test` 一些不满意的地方,这里面有个词比较有意思:[paper-cuts](https://en.wikipedia.org/wiki/Paper_cut_bug),中文直接翻译就是“被纸张划的伤痕”,在软件领域特指:影响用户体验的小缺陷,虽然不严重,但是比较烦人。 +- [My hopes and dreams for ‘zig + test’](https://zig.news/slimsag/my-hopes-and-dreams-for-zig-test-2pkh)。作者对 + `zig test` + 一些不满意的地方,这里面有个词比较有意思:[paper-cuts](https://en.wikipedia.org/wiki/Paper_cut_bug),中文直接翻译就是“被纸张划的伤痕”,在软件领域特指:影响用户体验的小缺陷,虽然不严重,但是比较烦人。 - ![paper-cuts](/images/paper-cuts.webp) +```=html +
    -- [视频][memory safety in c++, zig, & rust (part 1) - youtube](https://www.youtube.com/watch?v=qeiRGbYCD-0) -- [Writing a SQL database, take two: Zig and RocksDB](https://notes.eatonphil.com/zigrocks-sql.html) 。本文作者是 TigerBeetle 的联合创始人 Phil,这篇文章主要演示了基于 Zig 做 RocksDB 的 binding,并在此基础上,增加 SQL 层,实现简单的 CRUD 功能。代码地址:[eatonphil/zigrocks](https://github.com/eatonphil/zigrocks) -- [Debugging undefined behavior caught by Zig](https://devlog.hexops.com/2022/debugging-undefined-behavior/)。Hexops 官博,本文讨论了一个有意思的问题:UBSan 的一个 bug 导致 [Mach engine](https://machengine.org/) 的测试失败,更准确说是 UBSan 需要内存地址对齐来工作,但是现在主流的处理器(x86、ARM、RISC-V 等)都可以处理非对齐的地址访问。 -- [packed struct field order reversed? : Zig](https://www.reddit.com/r/Zig/comments/yvl60t/packed_struct_field_order_reversed/) -- [Using rr to quickly debug memory corruption](https://zig.news/david_vanderson/using-rr-to-quickly-debug-memory-corruption-2539),本文作者通过使用 [rr](https://rr-project.org/) 这个工具来排查内存损坏的问题 -- [A Programmer-Friendly I/O Abstraction Over io_uring and kqueue](https://tigerbeetle.com/blog/a-friendly-abstraction-over-iouring-and-kqueue/)。 TigerBeetle 官博,[HN 讨论](https://news.ycombinator.com/item?id=33721075) -- [How zig-spoon (and lots of coffee) helped me sort thousands of pictures](https://zig.news/lhp/how-zig-spoon-and-lots-of-coffee-helped-me-sort-thousands-of-pictures-4gkj)。本文作者通过 [spoon](https://sr.ht/~leon_plickat/zig-spoon/) 这个库开发了一个帮助自己进行图片打标的 UI 工具, + paper-cuts -# 项目/工具 + +
    +``` + +- \[视频\]\[memory safety in c++, zig, & rust (part 1) - + youtube\](https://www.youtube.com/watch?v=qeiRGbYCD-0) + +- [Writing a SQL database, take two: Zig and + RocksDB](https://notes.eatonphil.com/zigrocks-sql.html) 。本文作者是 + TigerBeetle 的联合创始人 Phil,这篇文章主要演示了基于 Zig 做 RocksDB + 的 binding,并在此基础上,增加 SQL 层,实现简单的 CRUD + 功能。代码地址:[eatonphil/zigrocks](https://github.com/eatonphil/zigrocks) -- [Zig 程序设计语言中文手册](https://sxwangzhiwen.github.io/zigcndoc/zigcndoc.html) GitHub 用户 [@sxwangzhiwen](https://github.com/sxwangzhiwen) 制作,论坛[相关讨论](https://github.com/zigcc/forum/discussions/36)。 -- [dantecatalfamo/zig-dns: Experimental DNS library implemented in zig](https://github.com/dantecatalfamo/zig-dns) -- [Neovim Zig Plugin written in Lua](https://github.com/CadeMichael/zig.nvim) -- [trace.zig: A small and simple tracing client library](https://zig.news/huntrss/tracezig-a-small-and-simple-tracing-client-library-2ffj),项目地址:[Zig tracing / trace.zig](https://gitlab.com/zig_tracing/trace.zig) -- [Zig Support plugin for IntelliJ and CLion version 0.2.0 released](https://zig.news/marioariasc/zig-support-plugin-for-intellij-and-clion-version-020-released-3g06) +- [Debugging undefined behavior caught by + Zig](https://devlog.hexops.com/2022/debugging-undefined-behavior/)。Hexops + 官博,本文讨论了一个有意思的问题:UBSan 的一个 bug 导致 [Mach + engine](https://machengine.org/) 的测试失败,更准确说是 UBSan + 需要内存地址对齐来工作,但是现在主流的处理器(x86、ARM、RISC-V + 等)都可以处理非对齐的地址访问。 + +- [packed struct field order reversed? : + Zig](https://www.reddit.com/r/Zig/comments/yvl60t/packed_struct_field_order_reversed/) + +- [Using rr to quickly debug memory + corruption](https://zig.news/david_vanderson/using-rr-to-quickly-debug-memory-corruption-2539),本文作者通过使用 + [rr](https://rr-project.org/) 这个工具来排查内存损坏的问题 + +- [A Programmer-Friendly I/O Abstraction Over io_uring and + kqueue](https://tigerbeetle.com/blog/a-friendly-abstraction-over-iouring-and-kqueue/)。 + TigerBeetle 官博,[HN + 讨论](https://news.ycombinator.com/item?id=33721075) + +- [How zig-spoon (and lots of coffee) helped me sort thousands of + pictures](https://zig.news/lhp/how-zig-spoon-and-lots-of-coffee-helped-me-sort-thousands-of-pictures-4gkj)。本文作者通过 + [spoon](https://sr.ht/~leon_plickat/zig-spoon/) + 这个库开发了一个帮助自己进行图片打标的 UI 工具, + +# 项目/工具 -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2022-11-01..2022-12-01) +- [Zig + 程序设计语言中文手册](https://sxwangzhiwen.github.io/zigcndoc/zigcndoc.html) + GitHub 用户 [@sxwangzhiwen](https://github.com/sxwangzhiwen) + 制作,论坛[相关讨论](https://github.com/zigcc/forum/discussions/36)。 +- [dantecatalfamo/zig-dns: Experimental DNS library implemented in + zig](https://github.com/dantecatalfamo/zig-dns) +- [Neovim Zig Plugin written in + Lua](https://github.com/CadeMichael/zig.nvim) +- [trace.zig: A small and simple tracing client + library](https://zig.news/huntrss/tracezig-a-small-and-simple-tracing-client-library-2ffj),项目地址:[Zig + tracing / trace.zig](https://gitlab.com/zig_tracing/trace.zig) +- [Zig Support plugin for IntelliJ and CLion version 0.2.0 + released](https://zig.news/marioariasc/zig-support-plugin-for-intellij-and-clion-version-020-released-3g06) + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-11-01..2022-12-01) diff --git a/content/monthly/202212.smd b/content/monthly/202212.smd index efd16e7..a9943e1 100644 --- a/content/monthly/202212.smd +++ b/content/monthly/202212.smd @@ -1,6 +1,6 @@ --- .title = "202212 | TBD", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2022-12-11T14:11:38+0800"), .author = "ZigCC", .layout = "monthly.shtml", .draft = false, @@ -8,8 +8,9 @@ # 观点/教程 -- [Goodbye to the C++ Implementation of Zig ⚡ Zig Programming Language](https://ziglang.org/news/goodbye-cpp/) +- [Goodbye to the C++ Implementation of Zig ⚡ Zig Programming + Language](https://ziglang.org/news/goodbye-cpp/) # 项目/工具 -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2022-12-01..2023-01-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-12-01..2023-01-01) diff --git a/content/monthly/202301.smd b/content/monthly/202301.smd index 8cfdf88..8d1419e 100644 --- a/content/monthly/202301.smd +++ b/content/monthly/202301.smd @@ -4,13 +4,17 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2023-01-31T20:05:19+0800" }, --- -# [0.10.1 版本发布]($section.id('0.10.1 版本发布')) -一个小版本,主要是 bugfix。最主要的功能是:[Package Manager MVP](https://github.com/ziglang/zig/pull/14265),Zig 终于开始支持包管理了! -不过才刚刚开始,有一个[面板](https://github.com/ziglang/zig/projects/4)来跟踪相关 issue 进度。使用的配置文件是 `build.zig.ini` ,格式如下: -```conf +# [0.10.1](https://ziglang.org/download/0.10.1/release-notes.html) 版本发布 + +一个小版本,主要是 bugfix。最主要的功能是:[Package Manager +MVP](https://github.com/ziglang/zig/pull/14265),Zig +终于开始支持包管理了! +不过才刚刚开始,有一个[面板](https://github.com/ziglang/zig/projects/4)来跟踪相关 +issue 进度。使用的配置文件是 `build.zig.ini` ,格式如下: + +``` conf [package] name=libffmpeg version=5.1.2 @@ -25,8 +29,10 @@ name=libmp3lame url=https://github.com/andrewrk/libmp3lame/archive/497568e670bfeb14ab6ef47fb6459a2251358e43.tar.gz hash=9ba4f49895b174a3f918d489238acbc146bd393575062b2e3be33488b688e36f ``` + `build.zig` 引用方式: -```zig + +``` zig const std = @import("std"); pub fn build(b: *std.build.Builder) void { @@ -46,22 +52,50 @@ pub fn build(b: *std.build.Builder) void { lib.install(); } ``` + 其他关注点: -- LLVM 升级到 [15.0.7](http://releases.llvm.org/15.0.7/docs/ReleaseNotes.html) + +- LLVM 升级到 + [15.0.7](http://releases.llvm.org/15.0.7/docs/ReleaseNotes.html) - 是 0.10.x 的最后一个 release 版本 -# [观点/教程]($section.id('观点/教程')) -- [Code study: interface idioms/patterns in zig standard libraries](https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj) :: 由于 Zig 目前还不支持接口抽空,本文介绍了标准库中来实现类似功能的五种方式 -- [A Zig Diary](https://kihlander.net/post/a-zig-diary/) :: 作者分享了对 Zig 的使用体验 -- [Why Accounting Needs Its Own Database with Joran Greef of Tiger Beetle](https://datastackshow.com/podcast/why-accounting-needs-its-own-database-with-joran-greef-of-tiger-beetle/) :: 播客分享 -- [Crossplatform JNI builds with Zig](https://0110.be/posts/Crossplatform_JNI_builds_with_Zig) :: 又一个使用 Zig 作为交叉编译的例子 - -# [项目/工具]($section.id('项目/工具')) -- [Introducing ⚡zap⚡ - blazingly fast backends in zig](https://zig.news/renerocksai/introducing-zap-blazingly-fast-backends-in-zig-3jhh) :: Zap 是 Zig 对 [facil.io - The C Web Application Framework](https://facil.io/) 的封装,本文算是对它的宣传。 -- [Indexing every Zig for great justice](https://zig.news/auguste/indexing-every-zig-for-great-justice-4l1h) :: 本文介绍了另一种语言服务器协议(LSP):SCIP,并用 zig 实现。项目处于早期阶段。 -- [dantecatalfamo/zig-git](https://github.com/dantecatalfamo/zig-git) :: Implementing git structures and functions in zig -- [axiomhq/zig-hyperloglog](https://github.com/axiomhq/zig-hyperloglog) :: Zig library for HyperLogLog estimation -- [This Week In Zig](https://thisweekinzig.mataroa.blog/) :: 一个介绍 Zig 的周刊,主要是 master 分支上的改动 - -# [Zig 语言更新]($section.id('Zig 语言更新')) -- [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2023-01-01..2023-02-01) +# 观点/教程 + +[Code study: interface idioms/patterns in zig standard +libraries](https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj) +由于 Zig +目前还不支持接口抽空,本文介绍了标准库中来实现类似功能的五种方式 + +[A Zig Diary](https://kihlander.net/post/a-zig-diary/) +作者分享了对 Zig 的使用体验 + +[Why Accounting Needs Its Own Database with Joran Greef of Tiger +Beetle](https://datastackshow.com/podcast/why-accounting-needs-its-own-database-with-joran-greef-of-tiger-beetle/) +播客分享 + +[Crossplatform JNI builds with +Zig](https://0110.be/posts/Crossplatform_JNI_builds_with_Zig) +又一个使用 Zig 作为交叉编译的例子 + +# 项目/工具 + +[Introducing ⚡zap⚡ - blazingly fast backends in +zig](https://zig.news/renerocksai/introducing-zap-blazingly-fast-backends-in-zig-3jhh) +Zap 是 Zig 对 [facil.io - The C Web Application +Framework](https://facil.io/) 的封装,本文算是对它的宣传。 + +[Indexing every Zig for great +justice](https://zig.news/auguste/indexing-every-zig-for-great-justice-4l1h) +本文介绍了另一种语言服务器协议(LSP):SCIP,并用 zig +实现。项目处于早期阶段。 + +[dantecatalfamo/zig-git](https://github.com/dantecatalfamo/zig-git) +Implementing git structures and functions in zig + +[axiomhq/zig-hyperloglog](https://github.com/axiomhq/zig-hyperloglog) +Zig library for HyperLogLog estimation + +[This Week In Zig](https://thisweekinzig.mataroa.blog/) +一个介绍 Zig 的周刊,主要是 master 分支上的改动 + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-01-01..2023-02-01) diff --git a/content/monthly/202302.smd b/content/monthly/202302.smd index 53dd636..7083760 100644 --- a/content/monthly/202302.smd +++ b/content/monthly/202302.smd @@ -4,69 +4,110 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2023-01-31T20:05:19+0800" }, --- -# [包管理器进展]($section.id('包管理器进展')) -包管理器自 [#14265](https://github.com/ziglang/zig/pull/14265) 合并后一直在不断推进,以下两个是最主要的改变: -- [build system terminology update: package, project, module, dependency](https://github.com/ziglang/zig/issues/14307) +# 包管理器进展 + +包管理器自 [\#14265](https://github.com/ziglang/zig/pull/14265) +合并后一直在不断推进,以下两个是最主要的改变: + +- [build system terminology update: package, project, module, + dependency](https://github.com/ziglang/zig/issues/14307) 这里重新梳理了现在的术语,主要有以下几个: - - `package` 文件的集合,由文件的 hash 值唯一指定,一个 package 可能包含任意数目的 compilation artifacts 与 modules。 - - `dependency` 不同 package 之间的有向边,一个 package 可以有任意个依赖,一个 package 也可以用作任意项目的依赖 - - `module` 文件的集合,每一个模块都有一个 root 文件,在被 `@import` 时用到。 - - `compilation artifact` 编译构建产物,可以是 static library,dynamic library,an executable 或 an object file,对应之前版本的 `LibExeObjStep` -- [introduce Zig Object Notation and use it for the build manifest file (build.zig.zon)](https://github.com/ziglang/zig/pull/14523) + + - `package` 文件的集合,由文件的 hash 值唯一指定,一个 package + 可能包含任意数目的 compilation artifacts 与 modules。 + - `dependency` 不同 package 之间的有向边,一个 package + 可以有任意个依赖,一个 package 也可以用作任意项目的依赖 + - `module` 文件的集合,每一个模块都有一个 root 文件,在被 `@import` + 时用到。 + - `compilation artifact` 编译构建产物,可以是 static library,dynamic + library,an executable 或 an object file,对应之前版本的 + `LibExeObjStep` + +- [introduce Zig Object Notation and use it for the build manifest file + (build.zig.zon)](https://github.com/ziglang/zig/pull/14523) 使用 zon 格式替代之前的 ini,格式如下: -```zig -.{ - .name = "awesome-cli", - .version = "0.1.0", - .dependencies = .{ - .simargs = .{ - .url = "https://github.com/jiacai2050/simargs/archive/0a1a2afd072cc915009a063075743192fc6b1fd5.tar.gz", - .hash = "1220a6554eccb2e9a9d7d63047e062314851ffd11315b9e6d1b5e06a9dde3275f150", - }, - }, -} -``` + + ``` zig + .{ + .name = "awesome-cli", + .version = "0.1.0", + .dependencies = .{ + .simargs = .{ + .url = "https://github.com/jiacai2050/simargs/archive/0a1a2afd072cc915009a063075743192fc6b1fd5.tar.gz", + .hash = "1220a6554eccb2e9a9d7d63047e062314851ffd11315b9e6d1b5e06a9dde3275f150", + }, + }, + } + ``` + 一些使用了包管理的实际项目: - - [andrewrk/ffmpeg: ffmpeg with the build system replaced by zig](https://github.com/andrewrk/ffmpeg) - - [jiacai2050/loc: Lines of code in Zig](https://github.com/jiacai2050/loc),适配包管理的相关 [commit 修改](https://github.com/jiacai2050/loc/commit/7b01c09a4ba9d3ddc3d067cc6af654601a99035a) - - [PCRE2Project/pcre2: zig build support](https://github.com/PCRE2Project/pcre2/pull/206) - - [nikneym/ws: WebSocket library for Zig](https://github.com/nikneym/ws) - - [Zig package manager · Issue #6 · natecraddock/ziglua](https://github.com/natecraddock/ziglua/issues/6) - -也欢迎大家给自己熟悉的 C/C++ 项目提 PR 让其支持 zig build,让构建不再那么痛苦。 - -# [观点/教程]($section.id('观点/教程')) -- [How a Zig IDE Could Work](https://matklad.github.io/2023/02/10/how-a-zig-ide-could-work.html) -- [Zig tips: v0.11 std.build API / package manager changes | Hexops' devlog](https://devlog.hexops.com/2023/zig-0-11-breaking-build-changes/) -- [pcre2 support zig build](https://lobste.rs/s/zh3ulk/pcre2_support_zig_build) -- [Multi-Object For Loops + Struct-Of-Arrays](https://zig.news/andrewrk/multi-object-for-loops-data-oriented-design-41ob) -- [Zig's Curious Multi-Sequence For Loops](https://kristoff.it/blog/zig-multi-sequence-for-loops),[Lobster 评论](https://lobste.rs/s/ihf30a/zig_s_curious_multi_sequence_for_loops) + + - [andrewrk/ffmpeg: ffmpeg with the build system replaced by + zig](https://github.com/andrewrk/ffmpeg) + - [jiacai2050/loc: Lines of code in + Zig](https://github.com/jiacai2050/loc),适配包管理的相关 [commit + 修改](https://github.com/jiacai2050/loc/commit/7b01c09a4ba9d3ddc3d067cc6af654601a99035a) + - [PCRE2Project/pcre2: zig build + support](https://github.com/PCRE2Project/pcre2/pull/206) + - [nikneym/ws: WebSocket library for + Zig](https://github.com/nikneym/ws) + - [Zig package manager · Issue \#6 · + natecraddock/ziglua](https://github.com/natecraddock/ziglua/issues/6) + +也欢迎大家给自己熟悉的 C/C++ 项目提 PR 让其支持 zig +build,让构建不再那么痛苦。 + +# 观点/教程 + +- [How a Zig IDE Could + Work](https://matklad.github.io/2023/02/10/how-a-zig-ide-could-work.html) + +- [Zig tips: v0.11 std.build API / package manager changes | Hexops’ + devlog](https://devlog.hexops.com/2023/zig-0-11-breaking-build-changes/) + +- [pcre2 support zig + build](https://lobste.rs/s/zh3ulk/pcre2_support_zig_build) + +- [Multi-Object For Loops + + Struct-Of-Arrays](https://zig.news/andrewrk/multi-object-for-loops-data-oriented-design-41ob) + +- [Zig’s Curious Multi-Sequence For + Loops](https://kristoff.it/blog/zig-multi-sequence-for-loops/),[Lobster + 评论](https://lobste.rs/s/ihf30a/zig_s_curious_multi_sequence_for_loops) 上面这两篇的文章都是演示了最新的 for 语法,开始支持了 range: -```zig -for (0..4) |n| { - std.debug.print("{} ", .{n}); -} -``` + + ``` zig + for (0..4) |n| { + std.debug.print("{} ", .{n}); + } + ``` + 同时也支持了一次性迭代多个数组的功能: -```zig -var elems = [_][]const u8 { "water", "earth", "fire", "wind" }; -var nats = [_][]const u8 { "tribes", "kingdom", "nation", "nomads" }; - -for (elems, nats) |e, n| { - std.debug.print("{s} {s}\n", .{e, n}); -} -``` -- [Zig Bits 0x1: Returning slices from functions](https://blog.orhun.dev/zig-bits-01/) + + ``` zig + + var elems = [_][]const u8 { "water", "earth", "fire", "wind" }; + var nats = [_][]const u8 { "tribes", "kingdom", "nation", "nomads" }; + + for (elems, nats) |e, n| { + std.debug.print("{s} {s}\n", .{e, n}); + } + ``` + +- [Zig Bits 0x1: Returning slices from + functions](https://blog.orhun.dev/zig-bits-01/) 这篇文章演示了从一个函数内返回局部变量的问题与解法 -- [Smoking Hot Binary Search In Zig](https://blog.deckc.hair/2023-02-22-smoking-hot-binary-search-in-zig.html) -# [项目/工具]($section.id('项目/工具')) -- [Writing high-performance clients for TigerBeetle](https://tigerbeetle.com/blog/2023-02-21-writing-high-performance-clients-for-tigerbeetle/) +- [Smoking Hot Binary Search In + Zig](https://blog.deckc.hair/2023-02-22-smoking-hot-binary-search-in-zig.html) + +# 项目/工具 + +- [Writing high-performance clients for + TigerBeetle](https://tigerbeetle.com/blog/2023-02-21-writing-high-performance-clients-for-tigerbeetle/) -# [Zig 语言更新]($section.id('Zig 语言更新')) -- [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2023-02-01..2023-03-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-02-01..2023-03-01) diff --git a/content/monthly/202303.smd b/content/monthly/202303.smd index 5fea47b..44ab59c 100644 --- a/content/monthly/202303.smd +++ b/content/monthly/202303.smd @@ -4,17 +4,23 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2023-04-10T19:25:59+0800" }, --- -# [观点/教程]($section.id('观点/教程')) -- [Creating arbitrary error values by using error.Something syntax](https://www.reddit.com/r/zig/comments/11wmoky) :: 下面两种方式是等价的: - ```zig - const err = error.FileNotFound; - const err = (error {FileNotFound}).FileNotFound; - ``` -- [Errors and Zig](https://notes.eatonphil.com/errors-and-zig.html) :: 主要讲述了 Zig 中如何处理错误,如何携带上下文信息 - ```zig +# 观点/教程 + +[Creating arbitrary error values by using error.Something +syntax](https://www.reddit.com/r/zig/comments/11wmoky) +下面两种方式是等价的: + +``` zig +const err = error.FileNotFound; +const err = (error {FileNotFound}).FileNotFound; +``` + +[Errors and Zig](https://notes.eatonphil.com/errors-and-zig.html) +主要讲述了 Zig 中如何处理错误,如何携带上下文信息 + +``` zig var x = try thingThatCouldFail(); if (thingThatCouldFail()) |good_value| { @@ -24,60 +30,113 @@ if (thingThatCouldFail()) |good_value| { // do something that should fix it for the next time tries -= 1; } - ``` -- [Zig Bits 0x2: Using defer to defeat memory leaks](https://blog.orhun.dev/zig-bits-02/) :: 主要介绍了如何探测内存泄漏,如何用 `defer` 来规避 -- [Naive parallel map implementation in Rust and Zig](https://zigurust.gitlab.io/blog/naive-map/) -- [The Curious Case of a Memory Leak in a Zig program](https://iamkroot.github.io/blog/zig-memleak) -- [When Zig is safer and faster than Rust](https://zackoverflow.dev/writing/unsafe-rust-vs-zig/) :: 比较有趣的文章,作者使用 unsafe rust 与 zig 来实现一个 bytecode 解释器,比重要的一点是具备 mark-sweep 的 GC 功能。 -- [Meet Zig: The modern alternative to C | InfoWorld](https://www.infoworld.com/article/3689648/meet-the-zig-programming-language.html) :: 一篇科普 Zig 的文章 -- [Cross-Compiling and packaging C, Go and Zig projects with Nix](https://flyx.org/cross-packaging/) :: 文章介绍了如何 - 利用 Nix 进行交叉编译,对于 C 依赖,作者是通过修改 `pkg-config` 的 `*.pc` 来支持的 -- [Zig Quirks](https://www.openmymind.net/Zig-Quirks/) :: 介绍 Zig 的一些特点 -- [Zig And Rust](https://matklad.github.io/2023/03/26/zig-and-rust.html) - -# [项目/工具]($section.id('项目/工具')) -- [macovedj/doink](https://github.com/macovedj/doink) :: Making WebAssembly Components with Zig -- [craftlinks/zig_learn_opengl](https://github.com/craftlinks/zig_learn_opengl) :: : Follow the Learn-OpenGL book using Zig -- [b0bleet/zvisor](https://github.com/b0bleet/zvisor) :: Zig-based Hypervisor (WIP) -- [flouthoc/ztick](https://github.com/flouthoc/ztick) :: Tiny desktop utility to keep notes -- [ringtailsoftware/zig-wasm-audio-framebuffer](https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer) :: Examples of integrating Zig and Wasm for audio and graphics on the web - -# [Zig 语言更新]($section.id('Zig 语言更新')) -- [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2023-03-01..2023-04-01) -- [zig build: run steps in parallel #14647](https://github.com/ziglang/zig/pull/14647) :: 一个比较大的更新,编译支持了并发。由于改动比较大,该 PR 合并后出现了一些 bug,一个影响比较大的是 test 的构建方式不一样了。 - ```diff +``` + +[Zig Bits 0x2: Using defer to defeat memory +leaks](https://blog.orhun.dev/zig-bits-02/) +主要介绍了如何探测内存泄漏,如何用 `defer` 来规避 + +[Naive parallel map implementation in Rust and +Zig](https://zigurust.gitlab.io/blog/naive-map/) + +[The Curious Case of a Memory Leak in a Zig +program](https://iamkroot.github.io/blog/zig-memleak) + +[When Zig is safer and faster than +Rust](https://zackoverflow.dev/writing/unsafe-rust-vs-zig/) +比较有趣的文章,作者使用 unsafe rust 与 zig 来实现一个 bytecode +解释器,比较重要的一点是具备 mark-sweep 的 GC 功能。 + +[Meet Zig: The modern alternative to C | +InfoWorld](https://www.infoworld.com/article/3689648/meet-the-zig-programming-language.html) +一篇科普 Zig 的文章 + +[Cross-Compiling and packaging C, Go and Zig projects with +Nix](https://flyx.org/cross-packaging/) +文章介绍了如何 利用 Nix 进行交叉编译,对于 C 依赖,作者是通过修改 +`pkg-config` 的 `*.pc` 来支持的 + +[Zig Quirks](https://www.openmymind.net/Zig-Quirks/) +介绍 Zig 的一些特点 + +[Zig And Rust](https://matklad.github.io/2023/03/26/zig-and-rust.html) + +# 项目/工具 + +[macovedj/doink](https://github.com/macovedj/doink) +Making WebAssembly Components with Zig + +[craftlinks/ziglearnopengl](https://github.com/craftlinks/zig_learn_opengl) + +Follow the Learn-OpenGL book using Zig + + +[b0bleet/zvisor](https://github.com/b0bleet/zvisor) +Zig-based Hypervisor (WIP) + +[flouthoc/ztick](https://github.com/flouthoc/ztick) +Tiny desktop utility to keep notes + +[ringtailsoftware/zig-wasm-audio-framebuffer](https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer) +Examples of integrating Zig and Wasm for audio and graphics on the web + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-03-01..2023-04-01) + +[zig build: run steps in parallel +\#14647](https://github.com/ziglang/zig/pull/14647) +一个比较大的更新,编译支持了并发。由于改动比较大,该 PR 合并后出现了一些 +bug,一个影响比较大的是 test 的构建方式不一样了。 + +``` diff - test_step.dependOn(&exe_tests.step); + test_step.dependOn(&exe_tests.run().step); - ``` -之前老方式写的 `test_step` 在新版本不会再执行了。这样对于 Build 系统来说其实是更合理了。 +``` + +之前老方式写的 `test_step` 在新版本不会再执行了。这样对于 Build +系统来说其实是更合理了。 -现在 `addTest` 和 `addExecutable` 一样,输出都是 `CompileStep` ,它默认不会执行,需要调用 `run()` 拿到 run step 才可以。 +现在 `addTest` 和 `addExecutable` 一样,输出都是 `CompileStep` +,它默认不会执行,需要调用 `run()` 拿到 run step 才可以。 -# [Zig 构建系统介绍]($section.id('Zig 构建系统介绍')) -构建系统其实是 Zig 生态中比较重要的一环,和 Rust 不同,Zig 即是一门编程语言,也是一个系列工具链,比如编译 Zig 时,没有 zigc 这个二进制文件,用的是 `zig build-exe` 或 `build-lib` 。 +# Zig 构建系统介绍 -`build.zig` 的作用就是提供了一套声明式 API 来构造 `build-exe` 的参数。这里面有一个核心概念: `Step` ,它构成了一个有向无环图,用来驱动整个编译过程。 +构建系统其实是 Zig 生态中比较重要的一环,和 Rust 不同,Zig +即是一门编程语言,也是一系列工具链,比如编译 Zig 时,没有 zigc +这个二进制文件,用的是 `zig build-exe` 或 `build-lib` 。 + +`build.zig` 的作用就是提供了一套声明式 API 来构造 `build-exe` +的参数。这里面有一个核心概念: `Step` +,它构成了一个有向无环图,用来驱动整个编译过程。 每个 Step 做的事情是由 `MakeFn` 定义的,它的签名是: -```zig + +``` zig pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void; ``` -但一般来说,我们并不需要自己去实现 MakeFn,内置的 Step 已经可以满足大部分需求,比如: +但一般来说,我们并不需要自己去实现 MakeFn,内置的 Step +已经可以满足大部分需求,比如: + - `CompileStep` 编译二进制、动态链接库、静态链接库 -- `InstallArtifactStep` 把编译生成的文件复制到 `zig-out` 中 +- `InstallArtifactStep` 把编译生成的文件复制到 `zig-out` 中 - `ObjCopyStep` 执行 objcopy 命令 -- `OptionsStep` 可以用来定义编译时的一些常量,作为 module 被当前程序使用,比如把当前项目的构建时间、Git 信息写入到代码中。[类似于 Go](https://www.digitalocean.com/community/tutorials/using-ldflags-to-set-version-information-for-go-applications) 里面的 ~go build -ldflags`"-X 'package_path.variable_name`new_value'"~ +- `OptionsStep` 可以用来定义编译时的一些常量,作为 module + 被当前程序使用,比如把当前项目的构建时间、Git + 信息写入到代码中。[类似于 + Go](https://www.digitalocean.com/community/tutorials/using-ldflags-to-set-version-information-for-go-applications) + 里面的 `go build -ldflags="-X 'package_path.variable_name=new_value'"` - `RunStep` 执行二进制 -Step 中有一类比较特殊,称为 TopLevelStep(简称 TLS),它们可以直接通过 `zig build {topLevelStep}` 的方式来执行,Zig 默认有两个 TLS: +Step 中有一类比较特殊,称为 TopLevelStep(简称 TLS),它们可以直接通过 +`zig build {topLevelStep}` 的方式来执行,Zig 默认有两个 TLS: + - install,安装二进制 - uninstall,卸载二进制 -```zig +``` zig const exe = b.addExecutable(.{ .name = "awesome", - .root_source_file ` .{ .path ` "src/main.zig" }, + .root_source_file = .{ .path = "src/main.zig" }, .target = target, .optimize = optimize, }); @@ -87,15 +146,22 @@ if (b.args) |args| { } const run_step = b.step("run-" , "Run " ++ name); run_step.dependOn(&run_cmd.step); - ``` -上面这段代码就定义了一个 TLS: `run` ,它依赖 exe 的执行 step,exe 本身又是个编译 step,因此在 `zig build run` 时,会依次执行: -```zig + +上面这段代码就定义了一个 TLS: `run` ,它依赖 exe 的执行 step,exe +本身又是个编译 step,因此在 `zig build run` 时,会依次执行: + +``` zig CompileStep --> RunStep --> TLS ``` -Zig 的编译系统设计的还是挺巧妙的,而且 `build.zig` 是新人接触 Zig 是打交道最多的代码,如果搞不清它的执行过程,一方面心里比较难受,另一实际方面是影响问题排查。 +Zig 的编译系统设计的还是挺巧妙的,而且 `build.zig` 是新人接触 Zig +是打交道最多的代码,如果搞不清它的执行过程,一方面心里比较难受,另一实际方面是影响问题排查。 + +如果读者还是对 `build.zig` +有所困惑,可以参考下面这两个文章,虽然有些过时,但是原理是一样的: -如果读者还是对 `build.zig` 有所困惑,可以参考下面这两个文章,虽然有些过时,但是原理是一样的: -- [Zig Build System Internals – Mitchell Hashimoto](https://mitchellh.com/zig/build-internals) -- [zig build explained - part 1 - Zig NEWS ⚡](https://zig.news/xq/zig-build-explained-part-1-59lf) +- [Zig Build System Internals\ – Mitchell + Hashimoto](https://mitchellh.com/zig/build-internals) +- [zig build explained - part 1 - Zig NEWS + ⚡](https://zig.news/xq/zig-build-explained-part-1-59lf) diff --git a/content/monthly/202304.smd b/content/monthly/202304.smd index 2b9cc96..73900f2 100644 --- a/content/monthly/202304.smd +++ b/content/monthly/202304.smd @@ -4,41 +4,106 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2023-09-03T20:10:11+0800" }, --- -# [重大事件]($section.id('重大事件')) -在 2023 四月份的 [Tiobe](https://www.tiobe.com/tiobe-index/) 指数上,Zig [排名 46](https://www.techrepublic.com/article/tiobe-index-language-rankings/),尽管 Loris 发推表示这个数字对 Zig 来说没什么实际意义,但对于多数吃瓜群众来说,这还是十分让人鼓舞的。 +# 重大事件 + +在 2023 四月份的 [Tiobe](https://www.tiobe.com/tiobe-index/) 指数上,Zig +[排名 +46](https://www.techrepublic.com/article/tiobe-index-language-rankings/),尽管 +Loris 发推表示这个数字对 Zig +来说没什么实际意义,但对于多数吃瓜群众来说,这还是十分让人鼓舞的。 > For people who heard about Zig just recently: > -> - Zig is not 2x faster than Rust, despite what recent benchmarks might lead you to believe. +> - Zig is not 2x faster than Rust, despite what recent benchmarks might +> lead you to believe. > -> - You won't find many Zig jobs for a few years still, despite the Tiobe stuff. +> - You won’t find many Zig jobs for a few years still, despite the +> Tiobe stuff. > -> - Don't join to the Zig community just to rant about Rust. +> - Don’t join to the Zig community just to rant about Rust. > -> — Loris Cro ⚡ (@croloris) [April 13, 2023](https://twitter.com/croloris/status/1646555550358831131) - -# [观点/教程]($section.id('观点/教程')) -- [When should I use an UNTAGGED Union?](https://zig.news/kristoff/when-should-i-use-an-untagged-union-56ek) :: Loris 的文章,作者利用访问 untagged union 的未赋值字段是一种 safety-checked UB 的行为,来解决数组成员被重新赋值过的情况。 -- [Data driven polymorphism](https://zig.news/rutenkolk/data-driven-polymorphism-45bk) :: 作者用 Zig 来实现 Clojure 语言中的 [defmulti](https://clojuredocs.org/clojure.core/defmulti),以达到『动态派发』的效果 -- [Testing and Files as Structs](https://zig.news/aryaelfren/testing-and-files-as-structs-n94) :: 作者演示了一个文件作为 struct 的效果,这样导入时就可以用 `const Node = @import("Node.zig")` 的方式了。 -- [Sneaky Error Payloads](https://zig.news/ityonemo/sneaky-error-payloads-1aka) :: 一种在错误中携带上下文信息的方式,上一期的月报也有类似讨论。 [Errors and Zig](https://notes.eatonphil.com/errors-and-zig.html) -- [Regular Expressions in Zig](https://www.openmymind.net/Regular-Expressions-in-Zig/) :: 由于 Zig 现在不支持 C 中的 bitfields,因此无法直接使用 Posix 的 `regex.h` ,这篇文章介绍了一种解决方法。 -- [Zig Build System](https://en.liujiacai.net/2023/04/13/zig-build-system/) :: 对 Zig build 系统的介绍 -- [Reasonable Bootstrap](https://matklad.github.io/2023/04/13/reasonable-bootstrap.html) :: 探讨了编译器如何实现自举的方式 -- [Data Oriented Parallel Value Interner](https://matklad.github.io/2023/04/23/data-oriented-parallel-value-interner.html) :: Matklad 探讨了如何实现一个高性能的 Interner -- [TigerStyle! (Or How To Design Safer Systems in Less Time)](https://www.youtube.com/watch?v=w3WYdYyjek4) :: Systems Distributed 23 视频。[B 站链接](https://www.bilibili.com/video/BV1fm4y1C7XL) -- [What Is a Database?](https://www.youtube.com/watch?v=MqbVoSs0lXk) :: Systems Distributed 23 视频,[B 站链接](https://www.bilibili.com/video/BV1gP41117zY/),作者博客:[Scattered Thoughts](https://www.scattered-thoughts.net/) - -# [项目/工具]($section.id('项目/工具')) -- [Coming Soon to a Zig Near You: HTTP Client](https://zig.news/nameless/coming-soon-to-a-zig-near-you-http-client-5b81) :: 对标准库 `std.http` 的介绍。 -- [Zig Bits 0x3: Mastering project management in Zig](https://blog.orhun.dev/zig-bits-03/) :: 介绍了如何更好地维护一个 Zig 项目,包括:新增依赖、增加测试覆盖率、增加文档、基于 GitHub Action 做持续集成等。 -- [ityonemo/zigler](https://github.com/ityonemo/zigler) :: zig nifs in elixir -- [Ziggifying Kilo](https://bingcicle.github.io/posts/ziggifying-kilo.html) :: 使用 Zig 重写 [kilo](https://github.com/antirez/kilo) 编辑器,目前仅能在 Linux 上运行 -- [jakubgiesler/VecZig](https://github.com/jakubgiesler/VecZig) :: Vector implementation in Zig -- [b0bleet/zvisor](https://github.com/b0bleet/zvisor) :: Zvisor is an open-source hypervisor written in the Zig programming language, which provides a modern and efficient approach to systems programming. - -# [Zig 语言更新]($section.id('Zig 语言更新')) -- [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) +> — Loris Cro ⚡ (@croloris) [April 13, +> 2023](https://twitter.com/croloris/status/1646555550358831131) + +\> + +# 观点/教程 + +[When should I use an UNTAGGED +Union?](https://zig.news/kristoff/when-should-i-use-an-untagged-union-56ek) +Loris 的文章,作者利用访问 untagged union 的未赋值字段是一种 +safety-checked UB 的行为,来解决数组成员被重新赋值过的情况。 + +[Data driven +polymorphism](https://zig.news/rutenkolk/data-driven-polymorphism-45bk) +作者用 Zig 来实现 Clojure 语言中的 +[defmulti](https://clojuredocs.org/clojure.core/defmulti),以达到『动态派发』的效果 + +[Testing and Files as +Structs](https://zig.news/aryaelfren/testing-and-files-as-structs-n94) +作者演示了一个文件作为 struct 的效果,这样导入时就可以用 +`const Node = @import("Node.zig")` 的方式了。 + +[Sneaky Error +Payloads](https://zig.news/ityonemo/sneaky-error-payloads-1aka) +一种在错误中携带上下文信息的方式,上一期的月报也有类似讨论。 [Errors and +Zig](https://notes.eatonphil.com/errors-and-zig.html) + +[Regular Expressions in +Zig](https://www.openmymind.net/Regular-Expressions-in-Zig/) +由于 Zig 现在不支持 C 中的 bitfields,因此无法直接使用 Posix 的 +`regex.h` ,这篇文章介绍了一种解决方法。 + +[Zig Build +System](https://en.liujiacai.net/2023/04/13/zig-build-system/) +对 Zig build 系统的介绍 + +[Reasonable +Bootstrap](https://matklad.github.io/2023/04/13/reasonable-bootstrap.html) +探讨了编译器如何实现自举的方式 + +[Data Oriented Parallel Value +Interner](https://matklad.github.io/2023/04/23/data-oriented-parallel-value-interner.html) +Matklad 探讨了如何实现一个高性能的 Interner + +[TigerStyle! (Or How To Design Safer Systems in Less +Time)](https://www.youtube.com/watch?v=w3WYdYyjek4) +Systems Distributed 23 视频。[B +站链接](https://www.bilibili.com/video/BV1fm4y1C7XL) + +[What Is a Database?](https://www.youtube.com/watch?v=MqbVoSs0lXk) +Systems Distributed 23 视频,[B +站链接](https://www.bilibili.com/video/BV1gP41117zY/),作者博客:[Scattered +Thoughts](https://www.scattered-thoughts.net/) + +# 项目/工具 + +[Coming Soon to a Zig Near You: HTTP +Client](https://zig.news/nameless/coming-soon-to-a-zig-near-you-http-client-5b81) +对标准库 `std.http` 的介绍。 + +[Zig Bits 0x3: Mastering project management in +Zig](https://blog.orhun.dev/zig-bits-03/) +介绍了如何更好地维护一个 Zig +项目,包括:新增依赖、增加测试覆盖率、增加文档、基于 GitHub Action +做持续集成等。 + +[ityonemo/zigler](https://github.com/ityonemo/zigler) +zig nifs in elixir + +[Ziggifying +Kilo](https://bingcicle.github.io/posts/ziggifying-kilo.html) +使用 Zig 重写 [kilo](https://github.com/antirez/kilo) 编辑器,目前仅能在 +Linux 上运行 + +[jakubgiesler/VecZig](https://github.com/jakubgiesler/VecZig) +Vector implementation in Zig + +[b0bleet/zvisor](https://github.com/b0bleet/zvisor) +Zvisor is an open-source hypervisor written in the Zig programming +language, which provides a modern and efficient approach to systems +programming. + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) diff --git a/content/monthly/202305.smd b/content/monthly/202305.smd index 179215b..c64d4e5 100644 --- a/content/monthly/202305.smd +++ b/content/monthly/202305.smd @@ -4,37 +4,100 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2023-06-17T13:32:13+0800" }, --- -# [重大事件]($section.id('重大事件')) +# 重大事件 + 这个月主要的事情就是 HTTP server 在标准库的增加了,具体可参考: -- [Coming Soon to a Zig Near You: HTTP Client](https://zig.news/nameless/coming-soon-to-a-zig-near-you-http-client-5b81) -- [http server in the standard library · Issue #910](https://github.com/ziglang/zig/issues/910) -# [观点/教程]($section.id('观点/教程')) -- [Integrating Zig and SwiftUI](https://mitchellh.com/writing/zig-and-swiftui) :: Mitchell 在用 Zig 实现了一个终端后,虽然没有把源码放出来,但是有了这个文章总结。 -- [Zig Language Server And Cancellation](https://matklad.github.io/2023/05/06/zig-language-server-and-cancellation.html) :: Matklad 对 ZLS 实现分析:如何快速响应用户的编辑命令,在作者来看,最主要是 server 端要能够及时取消已经过期的操作。 -- [Bootstrapping Uber's Infrastructure on arm64 with Zig](https://www.uber.com/en-IT/blog/bootstrapping-ubers-infrastructure-on-arm64-with-zig/) :: Zig 社区的老朋友 Motiejus Jakštys 在 Uber 的 arm 化过程中,探索了如何使用 Zig 提供对 Go 代码的交叉编译。 -- [The Worst Zig Version Manager](https://matklad.github.io/2023/06/02/the-worst-zig-version-manager.html) :: 在没有 Zig 环境的机器上,如何安装 Zig ?作者的思路是每个项目都附带一个脚本来做这件事,这种方式看似比较笨拙,但很有实用性。 -- [Writing an init system in a language I don't know](https://juliette.page/blog/init.html) :: 作者介绍了使用 Zig 开发一个 init 系统的经历,文中主要吐槽了现在 Zig 的文档严重不足 🙃,但看得出,作者依旧是喜欢 Zig 的。 -- [SIMD with Zig](https://www.openmymind.net/SIMD-With-Zig/) :: 作者演示了如何利用 SIMD 函数来改进 `indexOf` 函数 -- [Anytype Antics](https://zig.news/perky/anytype-antics-2398) :: 作者介绍了如何使用 `anytype` ,一些示例包括:Duck Type、Traits、Comptime Tagged Unions -- [Using Zig | My Initial Thoughts on Ziglang](https://www.youtube.com/watch?v=VU1h-h9doS8) :: 视频 -- [Zig: First Impressions](https://www.youtube.com/watch?v`kRrxbRLWsBo&feature`youtu.be) :: 视频 -- [(Possibly) LVGL in WebAssembly with Zig Compiler](https://lupyuen.codeberg.page/articles/lvgl3.html) :: -- [Initial Commit: Zig Build System](https://www.priver.dev/blog/zig/initial-commit-build-system/) :: -- [Writing DNS resolver in Zig](https://e-aakash.github.io/update/2023/06/04/resolv-dns-resolver-in-zig.html) :: -# [项目/工具]($section.id('项目/工具')) -- [Zig by Example](https://zigbyexample.github.io/) :: 非常好的学习资料。Learn How to use Zig's Standard Library, by small examples. -- [sleibrock/zigtoys](https://github.com/sleibrock/zigtoys) :: All about Zig + WASM and seeing what we can do -- [Aandreba/zigrc](https://github.com/Aandreba/zigrc) :: Zig reference-counted pointers inspired by Rust's Rc and Arc -- [Hanaasagi/struct](https://github.com/Hanaasagi/struct-env) :: Deserialize env vars into typesafe structs -- [tristanisham/minigrep](https://github.com/tristanisham/minigrep-zig) :: A Zig version of the Rust book's minigrep tutorial program -- [jsomedon/night.zig](https://github.com/jsomedon/night.zig) :: Simple tool that just install & update zig nightly. -- [4imothy/termy48](https://github.com/4imothy/termy48) :: A 2048 game to run in terminal -- [zig-html-example](https://github.com/bnl1/zig-html-example/blob/main/html.zig) :: 一个有趣的演示,利用 comptime 来定义 HTML 中的 Tag -- [KilianVounckx/rayz](https://github.com/KilianVounckx/rayz) :: 另一个 Raylib 的 bindings -- [mitchellh/zig](https://github.com/mitchellh/zig-objc) :: Objective-C runtime bindings for Zig (Zig calling ObjC). - -# [Zig 语言更新]($section.id('zig-update')) -[2023-04-01..2023-05-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) + +- [Coming Soon to a Zig Near You: HTTP + Client](https://zig.news/nameless/coming-soon-to-a-zig-near-you-http-client-5b81) +- [http server in the standard library · Issue + \#910](https://github.com/ziglang/zig/issues/910) + +# 观点/教程 + +[Integrating Zig and +SwiftUI](https://mitchellh.com/writing/zig-and-swiftui) +Mitchell 在用 Zig +实现了一个终端后,虽然没有把源码放出来,但是有了这个文章总结。 + +[Zig Language Server And +Cancellation](https://matklad.github.io/2023/05/06/zig-language-server-and-cancellation.html) +Matklad 对 ZLS +实现分析:如何快速响应用户的编辑命令,在作者来看,最主要是 server +端要能够及时取消已经过期的操作。 + +[Bootstrapping Uber’s Infrastructure on arm64 with +Zig](https://www.uber.com/en-IT/blog/bootstrapping-ubers-infrastructure-on-arm64-with-zig/) +Zig 社区的老朋友 Motiejus Jakštys 在 Uber 的 arm +化过程中,探索了如何使用 Zig 提供对 Go 代码的交叉编译。 + +[The Worst Zig Version +Manager](https://matklad.github.io/2023/06/02/the-worst-zig-version-manager.html) +在没有 Zig 环境的机器上,如何安装 Zig +?作者的思路是每个项目都附带一个脚本来做这件事,这种方式看似比较笨拙,但很有实用性。 + +[Writing an init system in a language I don’t +know](https://juliette.page/blog/init.html) +作者介绍了使用 Zig 开发一个 init 系统的经历,文中主要吐槽了现在 Zig +的文档严重不足 🙃,但看得出,作者依旧是喜欢 Zig 的。 + +[SIMD with Zig](https://www.openmymind.net/SIMD-With-Zig/) +作者演示了如何利用 SIMD 函数来改进 `indexOf` 函数 + +[Anytype Antics](https://zig.news/perky/anytype-antics-2398) +作者介绍了如何使用 `anytype` ,一些示例包括:Duck Type、Traits、Comptime +Tagged Unions + +[Using Zig | My Initial Thoughts on +Ziglang](https://www.youtube.com/watch?v=VU1h-h9doS8) +视频 + +[Zig: First +Impressions](https://www.youtube.com/watch?v=kRrxbRLWsBo&feature=youtu.be) +视频 + +[(Possibly) LVGL in WebAssembly with Zig +Compiler](https://lupyuen.codeberg.page/articles/lvgl3.html) + +[Initial Commit: Zig Build +System](https://www.priver.dev/blog/zig/initial-commit-build-system/) + +[Writing DNS resolver in +Zig](https://e-aakash.github.io/update/2023/06/04/resolv-dns-resolver-in-zig.html) + +# 项目/工具 + +[Zig by Example](https://zigbyexample.github.io/) +非常好的学习资料。Learn How to use Zig’s Standard Library, by small +examples. + +[sleibrock/zigtoys](https://github.com/sleibrock/zigtoys) +All about Zig + WASM and seeing what we can do + +[Aandreba/zigrc](https://github.com/Aandreba/zigrc) +Zig reference-counted pointers inspired by Rust’s Rc and Arc + +[Hanaasagi/struct](https://github.com/Hanaasagi/struct-env) +Deserialize env vars into typesafe structs + +[tristanisham/minigrep](https://github.com/tristanisham/minigrep-zig) +A Zig version of the Rust book’s minigrep tutorial program + +[jsomedon/night.zig](https://github.com/jsomedon/night.zig) +Simple tool that just install & update zig nightly. + +[4imothy/termy48](https://github.com/4imothy/termy48) +A 2048 game to run in terminal + +[zig-html-example](https://github.com/bnl1/zig-html-example/blob/main/html.zig) +一个有趣的演示,利用 comptime 来定义 HTML 中的 Tag + +[KilianVounckx/rayz](https://github.com/KilianVounckx/rayz) +另一个 Raylib 的 bindings + +[mitchellh/zig](https://github.com/mitchellh/zig-objc) +Objective-C runtime bindings for Zig (Zig calling ObjC). + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) diff --git a/content/monthly/202306.smd b/content/monthly/202306.smd index af11442..d15b430 100644 --- a/content/monthly/202306.smd +++ b/content/monthly/202306.smd @@ -4,73 +4,161 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2023-07-01T22:05:35+0800" }, --- -# [重大事件]($section.id('重大事件')) -一个是这个:[The Zig subreddit has closed](https://ziggit.dev/t/the-zig-subreddit-has-closed/679),现在 Ziggit 算是官方钦定的论坛了。 +# 重大事件 -另一个是月底出来的大新闻:[File for Divorce from LLVM · Issue #16270 · ziglang/zig](https://github.com/ziglang/zig/issues/16270) +一个是这个:[The Zig subreddit has +closed](https://ziggit.dev/t/the-zig-subreddit-has-closed/679),现在 +Ziggit 算是官方钦定的论坛了。 -这个 issue 主要讨论的是把 LLVM 从 Zig 中彻底移除,动机和优势都列在里面了,这里不再赘述,这里重点说下影响: -1. 去掉 C++/Objc 的支持, -2. 支持的 target 会变少 +另一个是月底出来的大新闻:[File for Divorce from LLVM · Issue \#16270 · +ziglang/zig](https://github.com/ziglang/zig/issues/16270) -从 issue 本身和 [Lobsters](https://lobste.rs/s/svhzj9/divorce_from_llvm)、[HN](https://news.ycombinator.com/item?id=36529456) 上的评论看,大家主要担忧的是对 C++ 的支持。由于非常多的基础软件都是构建在 C++ 之上的,如果没有了对 C++ 的支持,那么 Zig 作为工具链这一选择的可行性就大打折扣了,要知道 Zig 之前最主要的卖点就是这个,比如:[Maintain it With Zig](https://kristoff.it/blog/maintain-it-with-zig/)。 +这个 issue 主要讨论的是把 LLVM 从 Zig +中彻底移除,动机和优势都列在里面了,这里不再赘述,这里重点说下影响: -有人提议用 Zig 重写一个 C++ 前端不就好了?但这属于理论上可行,实际没有可操作性的,因为 C++ [太复杂](https://news.ycombinator.com/item?id=36532170)了。 +1. 去掉 C++/Objc 的支持, +2. 支持的 target 会变少 + +从 issue 本身和 +[Lobsters](https://lobste.rs/s/svhzj9/divorce_from_llvm)、[HN](https://news.ycombinator.com/item?id=36529456) +上的评论看,大家主要担忧的是对 C++ +的支持。由于非常多的基础软件都是构建在 C++ 之上的,如果没有了对 C++ +的支持,那么 Zig 作为工具链这一选择的可行性就大打折扣了,要知道 Zig +之前最主要的卖点就是这个,比如:[Maintain it With +Zig](https://kristoff.it/blog/maintain-it-with-zig/)。 + +有人提议用 Zig 重写一个 C++ +前端不就好了?但这属于理论上可行,实际没有可操作性的,因为 C++ +[太复杂](https://news.ycombinator.com/item?id=36532170)了。 + +其实 Zig 从 +[0.10.0](https://ziglang.org/download/0.10.0/release-notes.html) +版本开始,就一直在着手 [Self-Hosted +Compiler](https://ziglang.org/download/0.10.0/release-notes.html#Self-Hosted-Compiler) +的开发。看得出,Zig +团队一直在追求极致,从编译速度,到二进制大小(以下数字均来自 0.10.0 的 +release note): -其实 Zig 从 [0.10.0](https://ziglang.org/download/0.10.0/release-notes.html) 版本开始,就一直在着手 [Self-Hosted Compiler](https://ziglang.org/download/0.10.0/release-notes.html#Self-Hosted-Compiler) 的开发。看得出,Zig 团队一直在追求极致,从编译速度,到二进制大小(以下数字均来自 0.10.0 的 release note): - Wall Clock Time: 43 seconds to 40 seconds (7% faster) - Peak RSS: 9.6 GiB to 2.8 GiB (3.5x less memory used) -- As a point of comparison, a stripped release build of Zig with LLVM is 169 MiB, while without LLVM (but with all the code generation backends you see here) it is 4.4 MiB. +- As a point of comparison, a stripped release build of Zig with LLVM is + 169 MiB, while without LLVM (but with all the code generation backends + you see here) it is 4.4 MiB. + +这个 issue 在互联网上迅速引起了热烈讨论,当然少不了吃瓜群众,以至于 +Andrewk +又追加了[一条评论](https://github.com/ziglang/zig/issues/16270#issuecomment-1615388680): -这个 issue 在互联网上迅速引起了热烈讨论,当然少不了吃瓜群众,以至于 Andrewk 又追加了[一条评论](https://github.com/ziglang/zig/issues/16270#issuecomment-1615388680): +> I see a lot of speculation in this GitHub Issue from folks who are not +> involved in Zig in any way. I would respectfully ask you to please +> take such speculation elsewhere. This issue tracker is for focused +> technical discussion by those who are actually using Zig, today. The +> noise in this thread distracts from the valuable comments by users who +> are sharing their use cases for the relevant features of Zig. > -I see a lot of speculation in this GitHub Issue from folks who are not involved in Zig in any way. I would respectfully ask you to please take such speculation elsewhere. This issue tracker is for focused technical discussion by those who are actually using Zig, today. The noise in this thread distracts from the valuable comments by users who are sharing their use cases for the relevant features of Zig. - -..which, by the way, I'm one of. For example, my music player reboot branch depends on chromaprint which is, dun dun dun, C++ code. - -I'm not going to simultaneously shoot myself and valuable community members in the face by yanking a load-bearing feature out from underneath us, without any kind of upgrade path. It's a bit unfortunate that the Internet has taken that narrative and run with it. - -For example, one thing to explore, later - once all those boxes above are checked - is whether we can satisfy the C++ compilation use case, as well as the LLVM optimization use case, with the package manager. The results of this exploration will heavily impact the ultimate decision of whether to accept or reject this proposal. - -Please, relax. Nothing is going to happen overnight, and nothing is more important than making sure our esteemed Zig users' needs are taken care of, one way or another. Whatever happens will happen in due time, with due respect for real world projects. This proposal is aspirational - something to look forward to and consider in the coming years. - -微信群里不少小伙伴也在担忧这个 issue 会不会导致 Zig 的 fork,甚至灭亡。其实看了上面的评论大家就应该放心了,Zig 团队知道用户的需要,不会搬起石头砸自己的脚。 - -我觉得通过这个事件更能坚定我投资 Zig 的信心了,一个追求极致的团队,不需要我们吃瓜群众瞎操心,有这个时间不如去看看 Zig 的各种 backend 进展,能不能给 fix 几个 regression?! - -最后,即便 Zig 这个项目夭折了,我相信通过这个学习的过程也有助于提高我们对系统编程、编译器的理解,不是吗? -# [观点/教程]($section.id('观点/教程')) -- [A Note About Zig Books for the Zig Community](https://kristoff.it/blog/note-about-zig-books/) :: Loris Cro 的博客,由于现在 Zig 的关注度越来越高,一些出版社开始联系社区的人出一本 Zig 的书,Loris 这里阐述了与出版社合作的利弊,以及他也在写一本关于 Zig 的书 `Intro to Zig / systems programming` ,由于 Zig 还是不停的开发中,因为书中会尽量少的去涉及 stdlib 的内容。 -- [How far away is 0.11 really?](https://ziggit.dev/t/how-far-away-is-0-11-really/744) :: 社区用户对 0.11 版本发布时间的疑问? -- [Mach: providing an ecosystem of C libraries using the Zig package manager](https://devlog.hexops.com/2023/mach-ecosystem-c-libraries/) :: 作者在文章讲述了利用 Zig 来打包 C 依赖的优势。 -- [The Seamstress Event Loop In Zig](https://ryleealanza.org/2023/06/21/The-Seamstress-Event-Loop-in-Zig.html) :: -- [Metaprogramming in Zig and parsing CSS](https://notes.eatonphil.com/2023-06-19-metaprogramming-in-zig-and-parsing-css.html) :: -- [Embed git commit in Zig programs](https://en.liujiacai.net/2023/06/29/embed-git-commit-in-zig/) :: 把 git 的 commit id 嵌入项目中非常有助于问题排查 -- [Problems of C, and how Zig addresses them](https://avestura.dev/blog/problems-of-c-and-how-zig-addresses-them) :: 不错的入门资料,主要内容: - - Comptime over Textual Replacement Preprocessing - - Memory Management, and Zig Allocators - - Billion dollar mistake vs Zig Optionals - - Pointer arithmetics vs Zig Slices - - Explicit memory alignment - - Arrays as values - - Error handling - - Everything is an expression - - C has a more complex syntax to deal with -- [Minimal Linux VM cross compiled with Clang and Zig](https://richiejp.com/zig-cross-compile-ltp-ltx-linux) :: 一篇有意思的文章,作者的任务是跑 Linux kernel 的 test,为了能够方便、简单的跑不同的平台,作者尝试用 Zig 的交叉编译能力来解决这个大难题 -- [Zig dangling pointers and segfaults](https://www.openmymind.net/Zig-Danling-Pointers/) :: -- [I think Zig is hard...but worth it](http://ratfactor.com/zig/hard) :: 安利 Zig 的文章,[HN 讨论](https://news.ycombinator.com/item?id=36149462) - http://ratfactor.com/zig/zighard_700px.jpg -# [项目/工具]($section.id('项目/工具')) -- [pondzdev/duckdb](https://github.com/pondzdev/duckdb-proxy/) :: 一个将 DuckDB 数据库通过 HTTP API 暴露出来的代理,主要是利用了 [DuckDB C API](https://duckdb.org/docs/api/c/api.html),示例: - ```bash +> ..which, by the way, I’m one of. For example, my music player reboot +> branch depends on chromaprint which is, dun dun dun, C++ code. +> +> I’m not going to simultaneously shoot myself and valuable community +> members in the face by yanking a load-bearing feature out from +> underneath us, without any kind of upgrade path. It’s a bit +> unfortunate that the Internet has taken that narrative and run with +> it. +> +> For example, one thing to explore, later - once all those boxes above +> are checked - is whether we can satisfy the C++ compilation use case, +> as well as the LLVM optimization use case, with the package manager. +> The results of this exploration will heavily impact the ultimate +> decision of whether to accept or reject this proposal. +> +> Please, relax. Nothing is going to happen overnight, and nothing is +> more important than making sure our esteemed Zig users’ needs are +> taken care of, one way or another. Whatever happens will happen in due +> time, with due respect for real world projects. This proposal is +> aspirational - something to look forward to and consider in the coming +> years. + +微信群里不少小伙伴也在担忧这个 issue 会不会导致 Zig 的 +fork,甚至灭亡。其实看了上面的评论大家就应该放心了,Zig +团队知道用户的需要,不会搬起石头砸自己的脚。 + +我觉得通过这个事件更能坚定我投资 Zig +的信心了,一个追求极致的团队,不需要我们吃瓜群众瞎操心,有这个时间不如去看看 +Zig 的各种 backend 进展,能不能给 fix 几个 regression?! + +最后,即便 Zig +这个项目夭折了,我相信通过这个学习的过程也有助于提高我们对系统编程、编译器的理解,不是吗? + +# 观点/教程 + +[A Note About Zig Books for the Zig +Community](https://kristoff.it/blog/note-about-zig-books/) +Loris Cro 的博客,由于现在 Zig +的关注度越来越高,一些出版社开始联系社区的人出一本 Zig 的书,Loris +这里阐述了与出版社合作的利弊,以及他也在写一本关于 Zig 的书 +`Intro to Zig / systems programming` ,由于 Zig +还是不停的开发中,因为书中会尽量少的去涉及 stdlib 的内容。 + +[How far away is 0.11 +really?](https://ziggit.dev/t/how-far-away-is-0-11-really/744) +社区用户对 0.11 版本发布时间的疑问? + +[Mach: providing an ecosystem of C libraries using the Zig package +manager](https://devlog.hexops.com/2023/mach-ecosystem-c-libraries/) +作者在文章讲述了利用 Zig 来打包 C 依赖的优势。 + +[The Seamstress Event Loop In +Zig](https://ryleealanza.org/2023/06/21/The-Seamstress-Event-Loop-in-Zig.html) + +[Metaprogramming in Zig and parsing +CSS](https://notes.eatonphil.com/2023-06-19-metaprogramming-in-zig-and-parsing-css.html) + +[Embed git commit in Zig +programs](https://en.liujiacai.net/2023/06/29/embed-git-commit-in-zig/) +把 git 的 commit id 嵌入项目中非常有助于问题排查 + +[Problems of C, and how Zig addresses +them](https://avestura.dev/blog/problems-of-c-and-how-zig-addresses-them) +不错的入门资料,主要内容: + +- Comptime over Textual Replacement Preprocessing +- Memory Management, and Zig Allocators +- Billion dollar mistake vs Zig Optionals +- Pointer arithmetics vs Zig Slices +- Explicit memory alignment +- Arrays as values +- Error handling +- Everything is an expression +- C has a more complex syntax to deal with + +[Minimal Linux VM cross compiled with Clang and +Zig](https://richiejp.com/zig-cross-compile-ltp-ltx-linux) +一篇有意思的文章,作者的任务是跑 Linux kernel 的 +test,为了能够方便、简单的跑不同的平台,作者尝试用 Zig +的交叉编译能力来解决这个大难题 + +[Zig dangling pointers and +segfaults](https://www.openmymind.net/Zig-Danling-Pointers/) + +[I think Zig is hard…but worth it](http://ratfactor.com/zig/hard) +安利 Zig 的文章,[HN +讨论](https://news.ycombinator.com/item?id=36149462) + + +# 项目/工具 + +[pondzdev/duckdb](https://github.com/pondzdev/duckdb-proxy/) +一个将 DuckDB 数据库通过 HTTP API 暴露出来的代理,主要是利用了 [DuckDB C +API](https://duckdb.org/docs/api/c/api.html),示例: + +``` bash # open the database in readonly (DB must exist in this case) $ ./duckdb-proxy --readonly db/mydatabase.duckdb $ curl http://localhost:8012/api/1/exec \ - -d '{"sql": "select version()"}' + -d '{"sql": "select version()"}' { "cols": [ "version()" @@ -81,10 +169,23 @@ $ curl http://localhost:8012/api/1/exec \ ] ] } - ``` -- [ryleelyman/seamstress](https://github.com/ryleelyman/seamstress) :: Lua monome + OSC scripting environment -- [Rust VS Zig benchmarks](https://programming-language-benchmarks.vercel.app/rust-vs-zig) :: Which programming language or compiler is faster -- [ziglang/shell](https://github.com/ziglang/shell-completions) :: Shell completions for the Zig compiler. -- [menduz/zig](https://github.com/menduz/zig-steamworks) :: Steamwork bindings for Zig. -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2023-05-01..2023-06-01) -- [WASI: Implement experimental threading support by Luukdegram · Pull Request #16207 · ziglang/zig](https://github.com/ziglang/zig/pull/16207) +``` + +[ryleelyman/seamstress](https://github.com/ryleelyman/seamstress) +Lua monome + OSC scripting environment + +[Rust VS Zig +benchmarks](https://programming-language-benchmarks.vercel.app/rust-vs-zig) +Which programming language or compiler is faster + +[ziglang/shell](https://github.com/ziglang/shell-completions) +Shell completions for the Zig compiler. + +[menduz/zig](https://github.com/menduz/zig-steamworks) +Steamwork bindings for Zig. + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-05-01..2023-06-01) + +- [WASI: Implement experimental threading support by Luukdegram · Pull + Request \#16207 · + ziglang/zig](https://github.com/ziglang/zig/pull/16207) diff --git a/content/monthly/202307.smd b/content/monthly/202307.smd index 55c06d7..e0b00e5 100644 --- a/content/monthly/202307.smd +++ b/content/monthly/202307.smd @@ -4,30 +4,51 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2023-08-09T08:15:59+0800" }, --- -# [重大事件]($section.id('重大事件')) -Andrewk 在最新的文章 -[The Upcoming Release Postponed Two More Weeks and Lacks Async Functions](https://ziglang.org/news/0.11.0-postponed-again/) 中指出,即将发布的 0.11 中将不会包含对异步的支持,现在异步是在 [stage2-async](https://github.com/ziglang/zig/tree/stage2-async) 这个分支上来开发,但是在开发过程中,总是有其他事情出现,然后 Andrewk 就先去搞这些事情了。因此,把对异步的支持放到 0.12 上了。 +# 重大事件 -另一件事是 [Jacob Young Joins the Core Zig Team](https://ziglang.org/news/welcome-jacob-young/),Core Team 迎来了另一位全职开发者,常用 ID [jacobly0](https://github.com/jacobly0),下面是他最近的提交记录: -- https://github.com/ziglang/zig/commits?author`jacobly0&since`2023-06-31 +Andrewk 在最新的文章 [The Upcoming Release Postponed Two More Weeks and +Lacks Async Functions](https://ziglang.org/news/0.11.0-postponed-again/) +中指出,即将发布的 0.11 中将不会包含对异步的支持,现在异步是在 +[stage2-async](https://github.com/ziglang/zig/tree/stage2-async) +这个分支上来开发,但是在开发过程中,总是有其他事情出现,然后 Andrewk +就先去搞这些事情了。因此,把对异步的支持放到 0.12 上了。 + +另一件事是 [Jacob Young Joins the Core Zig +Team](https://ziglang.org/news/welcome-jacob-young/),Core Team +迎来了另一位全职开发者,常用 ID +[jacobly0](https://github.com/jacobly0),下面是他最近的提交记录: + +- 恭喜 Core Team,又添一虎将! -# [观点/教程]($section.id('观点/教程')) -- [Copy Hunting | TigerBeetle](https://tigerbeetle.com/blog/2023-07-26-copy-hunting/) :: 比较有意思的文章,通过分析 LLVM 的 IR,来避免程序中不必要的 memcpy 以及如何减少 binary 体积 -- [Taking off with Zig: Putting the Z in Benchmark](https://double-trouble.dev/post/zbench/) :: Zig 入门文章,作者使用 Zig 来实现了一个 benchmark 库,里面有作者的感受,语言小巧,主要的语言特点:独特的错误处理、显式的内存控制、comptime 执行 -- [The New Wave of Programming Languages: Pony, Zig, Crystal, Vlang, and Julia](https://hackernoon.com/the-new-wave-of-programming-languages-exploring-the-hidden-gems) :: 多种语言的对比,Zig 部分的对比: + +# 观点/教程 + +[Copy Hunting | TigerBeetle](https://tigerbeetle.com/blog/2023-07-26-copy-hunting/) +比较有意思的文章,通过分析 LLVM 的 IR,来避免程序中不必要的 memcpy +以及如何减少 binary 体积 + +[Taking off with Zig: Putting the Z in Benchmark](https://double-trouble.dev/post/zbench/) +Zig 入门文章,作者使用 Zig 来实现了一个 benchmark +库,里面有作者的感受,语言小巧,主要的语言特点:独特的错误处理、显式的内存控制、comptime +执行 + +[The New Wave of Programming Languages: Pony, Zig, Crystal, Vlang, and Julia](https://hackernoon.com/the-new-wave-of-programming-languages-exploring-the-hidden-gems) +多种语言的对比,Zig 部分的对比: | Pros | Cons | -|--------------------------------------------+-----------------------------| +|--------------------------------------------|-----------------------------| | Excellent low-level control over code | Relatively new and evolving | | Emphasis on safety and reliability | Limited library support | | Good interoperability with other languages | Steep learning curve | -- [Parsing timestamps and generating RFC3339 dates in Zig](https://www.aolium.com/karlseguin/cf03dee6-90e1-85ac-8442-cf9e6c11602a) :: Zig 标准库里有返回 unixtime 时间戳的函数,但是没有格式化函数,作者这里给出了一种实现: - ```zig +[Parsing timestamps and generating RFC3339 dates in Zig](https://www.aolium.com/karlseguin/cf03dee6-90e1-85ac-8442-cf9e6c11602a) +Zig 标准库里有返回 unixtime +时间戳的函数,但是没有格式化函数,作者这里给出了一种实现: + +``` zig pub const DateTime = struct { year: u16, month: u8, @@ -75,9 +96,15 @@ pub fn fromTimestamp(ts: u64) DateTime { .second = @intCast(seconds_since_midnight % 60) }; } - ``` -- [Custom JSON serialization in Zig](https://www.aolium.com/karlseguin/46252c5b-587a-c419-be96-a0ccc2f11de4) :: Zig 里面虽然有很好的 JSON 序列化支持,但是有些时候我们需要自定义某个字段的解析,与 Go `json.Marshaller` 类似,Zig 会在序列化时查找类型的 `jsonStringify` 函数,通过实现这个函数就可以达到目的,文中给出了个示例: - ```zig +``` + +[Custom JSON serialization in Zig](https://www.aolium.com/karlseguin/46252c5b-587a-c419-be96-a0ccc2f11de4) +Zig 里面虽然有很好的 JSON +序列化支持,但是有些时候我们需要自定义某个字段的解析,与 Go +`json.Marshaller` 类似,Zig 会在序列化时查找类型的 `jsonStringify` +函数,通过实现这个函数就可以达到目的,文中给出了个示例: + +``` zig const NumericBoolean = struct { value: bool, @@ -86,17 +113,38 @@ const NumericBoolean = struct { return out.print("{s}", .{json}); } }; - ``` -- [Three Different Cuts](https://matklad.github.io/2023/07/16/three-different-cuts.html) :: matklad 的文章,介绍了 Rust、Go、Zig 三种语言的 Cut 实现 -- [Zig Bits 0x4: Building an HTTP client/server from scratch](https://blog.orhun.dev/zig-bits-04/) :: 介绍了 std.http 这个模块的使用 -- [We Put a Distributed Database In the Browser – And Made a Game of It](https://tigerbeetle.com/blog/2023-07-11-we-put-a-distributed-database-in-the-browser/) :: TigerBeetle 的新花样,把数据库搬到了 Web 上。[HN 讨论](https://news.ycombinator.com/item?id=36680535) -- [Zig: great design for great optimizations](https://zig.news/gwenzek/zig-great-design-for-great-optimizations-638) :: 比较有意思的文章,作者比较了 Clang、Zig 对相同逻辑代码生产的 LLVM IR,Zig 生成的 IR 更加精简 - -# [项目/工具]($section.id('项目/工具')) -- [Zig helped us move data to the Edge. Here are our impressions](https://blog.turso.tech/zig-helped-us-move-data-to-the-edge-here-are-our-impressions-67d3a9c45af4) :: [Turso](https://turso.tech/) 公司的官博,它们公司的产品时边缘数据库,自动同步 PG 的表到 Edge 端,减少访问的时延。在这篇文章里他们介绍了使用 Zig 编写 PostgreSQL 插件的经历,得益于 `translate-c` ,他们可以直接从已有的 C 代码开始构建他们的产品。插件地址:[pg_turso](https://github.com/turso-extended/pg_turso) -- [tensorush/meduza](https://github.com/tensorush/meduza) :: 🦎 🧜‍♀️ Zig codebase graph generator that emits a Mermaid class diagram -- [AndreaOrru/zen](https://github.com/AndreaOrru/zen) :: Experimental operating system written in Zig -- [EugenHotaj/zig_gpt2](https://github.com/EugenHotaj/zig_gpt2) :: GPT-2 inference engine written in Zig - -# [Zig 语言更新]($section.id('zig-update')) -[2023-06-01..2023-07-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-06-01..2023-07-01) +``` + +[Three Different Cuts](https://matklad.github.io/2023/07/16/three-different-cuts.html) +matklad 的文章,介绍了 Rust、Go、Zig 三种语言的 Cut 实现 + +[Zig Bits 0x4: Building an HTTP client/server from scratch](https://blog.orhun.dev/zig-bits-04/) +介绍了 std.http 这个模块的使用 + +[We Put a Distributed Database In the Browser – And Made a Game of It](https://tigerbeetle.com/blog/2023-07-11-we-put-a-distributed-database-in-the-browser/) +TigerBeetle 的新花样,把数据库搬到了 Web 上。[HN +讨论](https://news.ycombinator.com/item?id=36680535) + +[Zig: great design for great optimizations](https://zig.news/gwenzek/zig-great-design-for-great-optimizations-638) +比较有意思的文章,作者比较了 Clang、Zig 对相同逻辑代码生产的 LLVM +IR,Zig 生成的 IR 更加精简 + +# 项目/工具 + +[Zig helped us move data to the Edge. Here are our impressions](https://blog.turso.tech/zig-helped-us-move-data-to-the-edge-here-are-our-impressions-67d3a9c45af4) +[Turso](https://turso.tech/) +公司的官博,它们公司的产品时边缘数据库,自动同步 PG 的表到 Edge +端,减少访问的时延。在这篇文章里他们介绍了使用 Zig 编写 PostgreSQL +插件的经历,得益于 `translate-c` ,他们可以直接从已有的 C +代码开始构建他们的产品。插件地址:[pgturso](https://github.com/turso-extended/pg_turso) + +[tensorush/meduza](https://github.com/tensorush/meduza) +🦎 🧜‍♀️ Zig codebase graph generator that emits a Mermaid class diagram + +[AndreaOrru/zen](https://github.com/AndreaOrru/zen) +Experimental operating system written in Zig + +[EugenHotaj/ziggpt2](https://github.com/EugenHotaj/zig_gpt2) +GPT-2 inference engine written in Zig + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-06-01..2023-07-01) diff --git a/content/monthly/202308.smd b/content/monthly/202308.smd index 514ab6c..57a41c9 100644 --- a/content/monthly/202308.smd +++ b/content/monthly/202308.smd @@ -4,25 +4,36 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2023-09-04T22:05:21+0800" }, --- # [0.11 正式发布](https://ziglang.org/download/0.11.0/release-notes.html#x86-Backend) -0.11 终于在 8 月 4 号释出了,下面来看看它的一些重要改进吧。[HN 讨论](https://news.ycombinator.com/item?id=36995735) -## [Peer Type Resolution Improvements]($section.id('Peer Type Resolution Improvements')) -对等类型解析算法得到改进,下面是一些在 0.10 中不能解析,但在 0.11 中可以解析的例子: -| Peer Types | Resolved Type | -| [:s]const T, []T | []const T | -| E!*T, ?*T | E!?*T | -| [*c]T, @TypeOf(null) | [*c]T | -| ?u32, u8 | ?u32 | -| [2]u32, struct { u32, u32 } | [2]u32 | -| *const @TypeOf(.{}), []const u8 | []const u8 | - -而且现在使用 `@intCast` 这类 builtin 都只接受一个参数,目前类型根据上下文自动推断出来。 -## [Multi-Object For Loops]($section.id('Multi-Object For Loops')) + +0.11 终于在 8 月 4 号释出了,下面来看看它的一些重要改进吧。[HN +讨论](https://news.ycombinator.com/item?id=36995735) + +## Peer Type Resolution Improvements + +对等类型解析算法得到改进,下面是一些在 0.10 中不能解析,但在 0.11 +中可以解析的例子: + +| | | +|------------------------------------|---------------| +| Peer Types | Resolved Type | +| \[:s\]const T, \[\]T | \[\]const T | +| E!\*T, ?\*T | E!?\*T | +| \[\*c\]T, @TypeOf(null) | \[\*c\]T | +| ?u32, u8 | ?u32 | +| \[2\]u32, struct { u32, u32 } | \[2\]u32 | +| \*const @TypeOf(.{}), \[\]const u8 | \[\]const u8 | + +而且现在使用 `@intCast` 这类 builtin +都只接受一个参数,目前类型根据上下文自动推断出来。 + +## Multi-Object For Loops + 可以同时对多个对象进行遍历: -```zig + +``` zig // 之前 for (input) |x, i| { output[i] = x * 2; @@ -33,11 +44,15 @@ for (input, 0..) |x, i| { output[i] = x * 2; } ``` -## [@min and @max]($section.id('@min and @max')) + +## @min and @max + 主要有两个改动: -1. 这两个 builtin 现在支持任意多个参数 -2. 返回的类型,会尽可能的紧凑 -```zig + +1. 这两个 builtin 现在支持任意多个参数 +2. 返回的类型,会尽可能的紧凑 + +``` zig test "@min/@max refines result type" { const x: u8 = 20; // comptime-known var y: u64 = 12345; @@ -49,9 +64,12 @@ test "@min/@max refines result type" { comptime assert(@TypeOf(@min(x_rt, y)) == u8); } ``` -## [@inComptime]($section.id('@inComptime')) + +## @inComptime + 新加的 builtin,用于判断执行是否在 comptime 环境下执行: -```zig + +``` zig const global_val = blk: { assert(@inComptime()); break :blk 123; @@ -76,11 +94,17 @@ test "@inComptime" { try expectEqual(@as(u32, 2), f()); } ``` -## [类型转化相关 builtin 的重命名]($section.id('类型转化相关 builtin 的重命名')) -之前 `@xToY` 形式的 builtin 现在已经改成了 `@yFromX` ,这样的主要好处是便于阅读(从右向左),这是[草案](https://github.com/ziglang/zig/issues/6128)。 -## [Tuple 类型声明]($section.id('Tuple 类型声明')) + +## 类型转化相关 builtin 的重命名 + +之前 `@xToY` 形式的 builtin 现在已经改成了 `@yFromX` +,这样的主要好处是便于阅读(从右向左),这是[草案](https://github.com/ziglang/zig/issues/6128)。 + +## Tuple 类型声明 + 现在可以直接用无 field 名字的 struct 来声明 tuple 类型: -```zig + +``` zig test "tuple declarations" { const T = struct { u32, []const u8 }; var t: T = .{ 1, "foo" }; @@ -103,44 +127,104 @@ test "tuple declarations" { ``` 之前只能用 `std.meta.Tuple` 函数来定义: -```diff + +``` diff - const testcases = [_]std.meta.Tuple(&[_]type{ []const u8, []const u8, bool }){ + const testcases = [_]struct { []const u8, []const u8, bool }{ ``` -## [排序]($section.id('排序')) + +## 排序 + 现在排序算法分布两类: + - 稳定,blocksort 算法 -- 不稳定,[pdqsort](https://github.com/ziglang/zig/pull/15412) 算法,它结合了随机快速排序的快速平均情况和堆排序的快速最坏情况。 +- 不稳定,[pdqsort](https://github.com/ziglang/zig/pull/15412) + 算法,它结合了随机快速排序的快速平均情况和堆排序的快速最坏情况。 + 与堆排的快速最差情况相结合,同时在具有特定模式的输入上达到线性时间。 -## [Stack Unwinding]($section.id('Stack Unwinding')) -Zig 之前依赖 [frame pointer](https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers) 来做堆栈回卷,但它本身有些代价,因此线上环境可能会通过 `-fomit-frame-pointer` 将其禁用掉。 - -为了在这种情况下依然能够获取 panic 是的堆栈信息,Zig 现在支持了通过 DWARF unwind tables 和 MachO compact unwind information 来会恢复堆栈,详见:[#15823](https://github.com/ziglang/zig/pull/15823)。 -## [包管理]($section.id('包管理')) -0.11 首次正式引入了包管理器,具体解释可以参考:[Zig Build System](https://en.liujiacai.net/2023/04/13/zig-build-system/),而且很重要一点,step 之间可以[并发执行](https://ziglang.org/download/0.11.0/release-notes.html#Steps-Run-In-Parallel), -## [Bootstrapping]($section.id('Bootstrapping')) -C++ 实现的 Zig 编译器已经被彻底移除,这意味着 `-fstage1` 不再生效,Zig 现在只需要一个 2.4M 的 WebAssembly 文件和一个 C 编辑器即可,工作细节可以参考:[Goodbye to the C++ Implementation of Zig](https://ziglang.org/news/goodbye-cpp/)。 -## [代码生成]($section.id('代码生成')) -虽然 Zig 编译器现在还是主要使用 LLVM 来进行代码生成,但在这次发布中,其他几个后端也有了非常大的进步: -1. C 后端,行为测试通过 98%,而且生成的 C 代码兼容微软的 MSVC,用在了 bootstrapping 中 -2. x86 后端,行为测试通过 88% -3. aarch64 后端,刚开始 -4. WebAssembly 后端,行为测试通过 86%, -5. SPIR-V 后端,SPIR-V 是在 GPU 上运行的着色器(shader)和内核的字节码表示法。目前,Zig 的 SPIR-V 后端专注于为 OpenCL 内核生成代码,不过未来可能也会支持兼容 Vulkan 的着色器。 -## [增量编译]($section.id('增量编译')) -虽然这仍是一个高度 WIP 的功能,但这一版本周期中的许多改进为编译器的增量编译功能铺平了道路。其中最重要的是 [InternPool](https://github.com/ziglang/zig/pull/15569)。Zig 用户大多看不到这一改动,但它为编译器带来了许多好处,其中之一就是我们现在更接近增量编译了。增量编译将是 0.12.0 发布周期的重点。 -# [观点/教程]($section.id('观点/教程')) -- [Error Handling In Zig](https://www.aolium.com/karlseguin/4013ac14-2457-479b-e59b-e603c04673c8) :: 又一篇讨论错误处理的文章 -- [Commiting Type Crimes in Zig](https://www.1a-insec.net/blog/10-type-magic-in-zig/) :: 对 Zig 类型系统的另一种用法,有些和[邱奇数](https://zh.wikipedia.org/wiki/%E9%82%B1%E5%A5%87%E6%95%B0)类似。 -- [Zig in 100 Seconds](https://www.youtube.com/watch?v=kxT8-C1vmd4) :: Zig 宣传视频 -- [Zig Build System & How to Build Software From Source • Andrew Kelley • GOTO 2023](https://www.youtube.com/watch?v`vKKTMBoxpS8) :: Andrew 关于构建系统的视频,[B 站链接](https://www.bilibili.com/video/BV1Mh4y1K7yc/)、[Youbute](https://www.youtube.com/watch?v`vKKTMBoxpS8) -- [Wrap your NIF with Zig](https://rbino.com/posts/wrap-your-nif-with-zig/) :: NIF 是 Elixir 中进行 FFI 调用的方式,如果用原生 C 接口来用,会需要写很多胶水代码, - 作者这里用 comptime 特性来定义了一个 `make_nif_wrapper` 来简化 NIF 的实现,这个技巧在与 C 项目交互时十分有用。 -- [Types and the Zig Programming Language](https://matklad.github.io/2023/08/09/types-and-zig.html) :: matklad 对 Zig 类型系统的总结 -- [So Long, Twitter and Reddit](https://andrewkelley.me/post/goodbye-twitter-reddit.html) :: Andrew 的最新文章,远离社交平台! -- [WTF is Zig Comptime (and Inline)](https://zig.news/edyu/wtf-is-zig-comptime-and-inline-257b) :: -- [Taking off with Zig: Putting the Z in Benchmark — Double Trouble](https://double-trouble.dev/post/zbench/) :: -# [项目/工具]($section.id('项目/工具')) -- [Mach v0.2 released](https://devlog.hexops.com/2023/mach-v0.2-released/) -# [Zig 语言更新]($section.id('zig-update')) -[2023-07-01..2023-08-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-07-01..2023-08-01) + +## Stack Unwinding + +Zig 之前依赖 [frame +pointer](https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers) +来做堆栈回卷,但它本身有些代价,因此线上环境可能会通过 +`-fomit-frame-pointer` 将其禁用掉。 + +为了在这种情况下依然能够获取 panic 是的堆栈信息,Zig 现在支持了通过 +DWARF unwind tables 和 MachO compact unwind information +来会恢复堆栈,详见:[\#15823](https://github.com/ziglang/zig/pull/15823)。 + +## 包管理 + +0.11 首次正式引入了包管理器,具体解释可以参考:[Zig Build +System](https://en.liujiacai.net/2023/04/13/zig-build-system/),而且很重要一点,step +之间可以[并发执行](https://ziglang.org/download/0.11.0/release-notes.html#Steps-Run-In-Parallel), + +## Bootstrapping + +C++ 实现的 Zig 编译器已经被彻底移除,这意味着 `-fstage1` 不再生效,Zig +现在只需要一个 2.4M 的 WebAssembly 文件和一个 C +编辑器即可,工作细节可以参考:[Goodbye to the C++ Implementation of +Zig](https://ziglang.org/news/goodbye-cpp/)。 + +## 代码生成 + +虽然 Zig 编译器现在还是主要使用 LLVM +来进行代码生成,但在这次发布中,其他几个后端也有了非常大的进步: + +1. C 后端,行为测试通过 98%,而且生成的 C 代码兼容微软的 MSVC,用在了 + bootstrapping 中 +2. x86 后端,行为测试通过 88% +3. aarch64 后端,刚开始 +4. WebAssembly 后端,行为测试通过 86%, +5. SPIR-V 后端,SPIR-V 是在 GPU + 上运行的着色器(shader)和内核的字节码表示法。目前,Zig 的 SPIR-V + 后端专注于为 OpenCL 内核生成代码,不过未来可能也会支持兼容 Vulkan + 的着色器。 + +## 增量编译 + +虽然这仍是一个高度 WIP +的功能,但这一版本周期中的许多改进为编译器的增量编译功能铺平了道路。其中最重要的是 +[InternPool](https://github.com/ziglang/zig/pull/15569)。Zig +用户大多看不到这一改动,但它为编译器带来了许多好处,其中之一就是我们现在更接近增量编译了。增量编译将是 +0.12.0 发布周期的重点。 + +# 观点/教程 + +[Error Handling In Zig](https://www.aolium.com/karlseguin/4013ac14-2457-479b-e59b-e603c04673c8) +又一篇讨论错误处理的文章 + +[Commiting Type Crimes in Zig](https://www.1a-insec.net/blog/10-type-magic-in-zig/) +对 Zig +类型系统的另一种用法,有些和[邱奇数](https://zh.wikipedia.org/wiki/%E9%82%B1%E5%A5%87%E6%95%B0)类似。 + +[Zig in 100 Seconds](https://www.youtube.com/watch?v=kxT8-C1vmd4) +Zig 宣传视频 + +[Zig Build System & How to Build Software From Source • Andrew Kelley • GOTO 2023](https://www.youtube.com/watch?v=vKKTMBoxpS8) +Andrew 关于构建系统的视频,[B +站链接](https://www.bilibili.com/video/BV1Mh4y1K7yc/)、[Youbute](https://www.youtube.com/watch?v=vKKTMBoxpS8) + +[Wrap your NIF with Zig](https://rbino.com/posts/wrap-your-nif-with-zig/) +NIF 是 Elixir 中进行 FFI 调用的方式,如果用原生 C +接口来用,会需要写很多胶水代码, 作者这里用 comptime 特性来定义了一个 +`make_nif_wrapper` 来简化 NIF 的实现,这个技巧在与 C +项目交互时十分有用。 + +[Types and the Zig Programming Language](https://matklad.github.io/2023/08/09/types-and-zig.html) +matklad 对 Zig 类型系统的总结 + +[So Long, Twitter and Reddit](https://andrewkelley.me/post/goodbye-twitter-reddit.html) +Andrew 的最新文章,远离社交平台! + +[WTF is Zig Comptime (and Inline)](https://zig.news/edyu/wtf-is-zig-comptime-and-inline-257b) + +[Taking off with Zig: Putting the Z in Benchmark — Double Trouble](https://double-trouble.dev/post/zbench/) + +# 项目/工具 + +- [Mach v0.2 + released](https://devlog.hexops.com/2023/mach-v0.2-released/) + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-07-01..2023-08-01) diff --git a/content/monthly/202309.smd b/content/monthly/202309.smd index c81e5a6..82de0f5 100644 --- a/content/monthly/202309.smd +++ b/content/monthly/202309.smd @@ -4,58 +4,118 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2023-10-04T20:12:55+0800" }, --- -# [重大事件]($section.id('重大事件')) +# 重大事件 + ## [Bounties Damage Open Source Projects](https://ziglang.org/news/bounties-damage-open-source-projects/) -在 2023-09-11 号,Wasmerio CEO 创建了 [Support WASIX · Issue #17115](https://github.com/ziglang/zig/issues/17115),表示想赞助 Zig 开发者,让其更好地支持 WASIX 平台。 + +在 2023-09-11 号,Wasmerio CEO 创建了 [Support WASIX · Issue +\#17115](https://github.com/ziglang/zig/issues/17115),表示想赞助 Zig +开发者,让其更好地支持 WASIX 平台。 Andrew 与 Loris 在这篇文章中主要阐述了这么做为什么是伤害社区的行为: -1. 助长竞争,牺牲合作 -2. 在软件开发的商业管理方面,悬赏是一种极为简单的方法,这可能让开发者关注短期效益,忽视长期利益,比如维护成本。 -这篇文章其实很符合 Andrew 的理念,不想让过多的热钱涌入 Zig 社区,他更想保证 Zig 的独立性,这也是他们创办 [Software You Can Love](https://kristoff.it/blog/the-open-source-game/) 的初衷。 +1. 助长竞争,牺牲合作 +2. 在软件开发的商业管理方面,悬赏是一种极为简单的方法,这可能让开发者关注短期效益,忽视长期利益,比如维护成本。 + +这篇文章其实很符合 Andrew 的理念,不想让过多的热钱涌入 Zig +社区,他更想保证 Zig 的独立性,这也是他们创办 [Software You Can +Love](https://kristoff.it/blog/the-open-source-game/) 的初衷。 ## [Bun 1.0](https://bun.sh/blog/bun-v1.0) -面包终于出炉了!Bun 毫无疑问是 Zig 的明星项目,它在 2023-09-08 正式发布了 1.0 版本,这对开发者来说可能没什么太大的区别,毕竟就只是个 tag 而已,但是对于广大的用户来说,这无疑意味着可以在生产环境中去使用了。 + +面包终于出炉了!Bun 毫无疑问是 Zig 的明星项目,它在 2023-09-08 +正式发布了 1.0 版本,这对开发者来说可能没什么太大的区别,毕竟就只是个 +tag 而已,但是对于广大的用户来说,这无疑意味着可以在生产环境中去使用了。 Bun 并不简单的是个 Node.js 替代品,而是大而全的工具链: + - Transpilers,可以直接运行 `js` `jsx` `mjs` `ts` `tsx` `cjs` 文件 - Bundlers,可以直接替代 webpack、esbuild 等工具 -- Package managers,兼容 npm,识别 `package.json` 格式,可以替代:npm、yarn、 -- Testing libraries,内置 test runner,支持快照测试、模拟和代码覆盖,可以替代:jest、ts-test -# [观点/教程]($section.id('观点/教程')) -- [Kiesel Devlog #1: Now passing 25% of test262](https://linus.dev/posts/kiesel-devlog-1/) :: 另一个 devlog,作者写了一个 JS engine 用来学习 Zig,该作者是 SerenityOS 系统中,Ladybird 浏览器 JS 引擎 LibJS 的作者 -- [Talk: Introducing Ghostty and Some Useful Zig Patterns](https://mitchellh.com/writing/ghostty-and-useful-zig-patterns) :: Mitchell Hashimoto 在这篇文章里分享了开发终端 Ghostty 时用的 Zig 常用模式。计划在 2024 年发布 1.0 - - Comptime Interface - - Comptime Data Table Generation - - Comptime Type Generation - - [B 站视频地址](https://www.bilibili.com/video/BV1884y1D7gu/) -- [Learning Zig](https://www.openmymind.net/learning_zig/) :: 一个 Zig 教程,写的非常易懂,推荐每个 Zig 爱好者阅读。目录 - 1. Language Overview - Part 1 - 2. Language Overview - Part 2 - 3. Style guide - 4. Pointers - 5. Stack Memory - 6. Heap Memory & Allocators - 7. Generics - 8. Coding In Zig - 9. Conclusion -- [Debugging a Zig Test Failure](https://zinascii.com/2023/debugging-a-zig-test-failure.html) :: 非常硬核的文章,作者为了调查一个文件名太长的错误,使用了 DTrace 来探测内核函数的调用,对平时的问题排查非常有帮助 -- [Intercepting and modifying Linux system calls with ptrace](https://notes.eatonphil.com/2023-10-01-intercepting-and-modifying-linux-system-calls-with-ptrace.html) :: 用 Zig 来封装 Dtrace,用于跟踪子进程的 syscall -- [Managing Zig Versions with zvm: A Technical Dive](https://double-trouble.dev/post/zvm/) :: 另一个 Zig 版本管理工具,不过这次是用 Zig 写的了 -- [When Zig Outshines Rust -- Memory Efficient Enum Arrays](https://alic.dev/blog/dense-enums) :: 一个图文并茂的文章,重点推荐。作者这里通过分析 array of struct 的内存浪费情况,介绍了 Rust/Zig 中不同的解法,Zig 由于有强大的编译期元编程能力,能够更方便的实现 SoA(struct of arrays) - {{< figure src`"/images/struct-padding.webp" caption`"struct 数组的内存布局">}} - {{< figure src`"/images/soa-layout.webp" caption`"SoA 内存布局">}} -# [项目/工具]($section.id('项目/工具')) -- [ZigBrains](https://plugins.jetbrains.com/plugin/22456-zigbrains) :: A multifunctional Zig Programming Language plugin for the IDEA platform. -- [fulcrum-so/ziggy-pydust](https://github.com/fulcrum-so/ziggy-pydust) :: A toolkit for building Python extensions in Zig. -- [zig-curl](https://github.com/jiacai2050/zig-curl) :: Zig bindings to libcurl. -- [darkr4y/OffensiveZig](https://github.com/darkr4y/OffensiveZig) :: Some attempts at using [Zig](https://ziglang.org/) in penetration testing. -- [Announcing Zig support for Wasm Workers Server](https://wasmlabs.dev/articles/zig-support-on-wasm-workers-server/) :: Wasm Workers Server 是一个用于开发 serverless 应用的框架,近期增加了对 Zig 的支持,[使用文档](https://workers.wasmlabs.dev/docs/languages/zig)。 -- [dantecatalfamo/wireguard](https://github.com/dantecatalfamo/wireguard-config-manager) :: Command line wireguard configuration manager. -- [iacore/libredo](https://github.com/iacore/libredo) :: Reactive signal/Dependency tracking library in Zig. -- [buzz-language/buzz](https://github.com/buzz-language/buzz) :: A small/lightweight statically typed scripting language -# [Zig 语言更新]($section.id('zig-update')) -[2023-08-01..2023-09-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-08-01..2023-09-01) +- Package managers,兼容 npm,识别 `package.json` + 格式,可以替代:npm、yarn、 +- Testing libraries,内置 test + runner,支持快照测试、模拟和代码覆盖,可以替代:jest、ts-test + +# 观点/教程 + +[Kiesel Devlog \#1: Now passing 25% of test262](https://linus.dev/posts/kiesel-devlog-1/) +另一个 devlog,作者写了一个 JS engine 用来学习 Zig,该作者是 SerenityOS +系统中,Ladybird 浏览器 JS 引擎 LibJS 的作者 + +[Talk: Introducing Ghostty and Some Useful Zig Patterns](https://mitchellh.com/writing/ghostty-and-useful-zig-patterns) +Mitchell Hashimoto 在这篇文章里分享了开发终端 Ghostty 时用的 Zig +常用模式。计划在 2024 年发布 1.0 + +- Comptime Interface +- Comptime Data Table Generation +- Comptime Type Generation +- [B 站视频地址](https://www.bilibili.com/video/BV1884y1D7gu/) + +[Learning Zig](https://www.openmymind.net/learning_zig/) +一个 Zig 教程,写的非常易懂,推荐每个 Zig 爱好者阅读。目录 + +1. Language Overview - Part 1 +2. Language Overview - Part 2 +3. Style guide +4. Pointers +5. Stack Memory +6. Heap Memory & Allocators +7. Generics +8. Coding In Zig +9. Conclusion + +[Debugging a Zig Test Failure](https://zinascii.com/2023/debugging-a-zig-test-failure.html) +非常硬核的文章,作者为了调查一个文件名太长的错误,使用了 DTrace +来探测内核函数的调用,对平时的问题排查非常有帮助 + +[Intercepting and modifying Linux system calls with ptrace](https://notes.eatonphil.com/2023-10-01-intercepting-and-modifying-linux-system-calls-with-ptrace.html) +用 Zig 来封装 Dtrace,用于跟踪子进程的 syscall + +[Managing Zig Versions with zvm: A Technical Dive](https://double-trouble.dev/post/zvm/) +另一个 Zig 版本管理工具,不过这次是用 Zig 写的了 + +[When Zig Outshines Rust – Memory Efficient Enum Arrays](https://alic.dev/blog/dense-enums) +一个图文并茂的文章,重点推荐。作者这里通过分析 array of struct +的内存浪费情况,介绍了 Rust/Zig 中不同的解法,Zig +由于有强大的编译期元编程能力,能够更方便的实现 SoA(struct of arrays) +[struct +数组的内存布局]($image.siteAsset('images/struct-padding.webp')) + [SoA 内存布局]($image.siteAsset('images/soa-layout.webp')) + + +# 项目/工具 + +[ZigBrains](https://plugins.jetbrains.com/plugin/22456-zigbrains) +A multifunctional Zig Programming Language plugin for the IDEA platform. + +[fulcrum-so/ziggy-pydust](https://github.com/fulcrum-so/ziggy-pydust) +A toolkit for building Python extensions in Zig. + +[zig-curl](https://github.com/jiacai2050/zig-curl) +Zig bindings to libcurl. + +[darkr4y/OffensiveZig](https://github.com/darkr4y/OffensiveZig) +Some attempts at using [Zig](https://ziglang.org/) in penetration +testing. + +[Announcing Zig support for Wasm Workers Server](https://wasmlabs.dev/articles/zig-support-on-wasm-workers-server/) +Wasm Workers Server 是一个用于开发 serverless 应用的框架,近期增加了对 +Zig +的支持,[使用文档](https://workers.wasmlabs.dev/docs/languages/zig)。 + +[dantecatalfamo/wireguard](https://github.com/dantecatalfamo/wireguard-config-manager) +Command line wireguard configuration manager. + +[iacore/libredo](https://github.com/iacore/libredo) +Reactive signal/Dependency tracking library in Zig. + +[buzz-language/buzz](https://github.com/buzz-language/buzz) +A small/lightweight statically typed scripting language + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-08-01..2023-09-01) + +[Aro translate](https://github.com/ziglang/zig/pull/17221) +用 Zig 写的 [Aro](https://github.com/Vexu/arocc) 来替换 clang,来实现 +`translate-c` 的功能 diff --git a/content/monthly/202310.smd b/content/monthly/202310.smd index 3abbe62..fa51abd 100644 --- a/content/monthly/202310.smd +++ b/content/monthly/202310.smd @@ -4,16 +4,25 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2023-11-04T19:47:55+0800" }, --- -# [重大事件]($section.id('重大事件')) -# [观点/教程]($section.id('观点/教程')) -- [Notes From the Field: Learning Zig](https://registerspill.thorstenball.com/p/notes-from-the-field-learning-zig) :: Zig 初学者的使用经验分享 -- [Friendly Neighbor: A network service for Linux wake-on-demand, written in Zig](https://dgross.ca/blog/friendly-neighbor-announce/) :: 作者在这篇文章中分享了 - 用 Zig 重写之前 Ruby 写的一个网络工具,一方面是减轻资源消耗,另一方面是探索用"低级"语言来写程序。不错的案例分享。 -- [Zig Interfaces](https://www.openmymind.net/Zig-Interfaces/) :: 作者介绍了 Zig 中如何实现接口这个经常需要用到的功能。最后的实现也比较巧妙,结合 `anytype` 与 `*anyopaque` - ```zig +# 重大事件 + +# 观点/教程 + +[Notes From the Field: Learning Zig](https://registerspill.thorstenball.com/p/notes-from-the-field-learning-zig) +Zig 初学者的使用经验分享 + +[Friendly Neighbor: A network service for Linux wake-on-demand, written in Zig](https://dgross.ca/blog/friendly-neighbor-announce/) +作者在这篇文章中分享了 用 Zig 重写之前 Ruby +写的一个网络工具,一方面是减轻资源消耗,另一方面是探索用“低级”语言来写程序。不错的案例分享。 + +[Zig Interfaces](https://www.openmymind.net/Zig-Interfaces/) +作者介绍了 Zig +中如何实现接口这个经常需要用到的功能。最后的实现也比较巧妙,结合 +`anytype` 与 `*anyopaque` + +``` zig const std = @import("std"); const Writer = struct { @@ -63,24 +72,47 @@ const File = struct { pub fn main() !void { var file = try std.fs.createFileAbsolute("/tmp/demo.txt", .{}); - var my_file ` File{ .fd ` file.handle }; + var my_file = File{ .fd = file.handle }; const writer = my_file.writer(); try writer.writeAll("hello world"); } - ``` -- [io_uring basics: Writing a file to disk](https://notes.eatonphil.com/2023-10-19-write-file-to-disk-with-io_uring.html) :: 作者演示了 io_uring 在 Go 与 Zig 中的基本使用,下面表格是一些测试数据 -| method | avg_time | avg_throughput | -|---------------------+----------------------+----------------| -| iouring_128_entries | 0.2756831357s | 3.8GB/s | -| iouring_1_entries | 0.27575404880000004s | 3.8GB/s | -| blocking | 0.2833337046s | 3.7GB/s | -- [Zig is now also a Windows resource compiler](https://www.ryanliptak.com/blog/zig-is-a-windows-resource-compiler/) :: 相当硬核的文章,作者最近给 Zig 贡献了一个大功能:支持 Windows 资源定义文件的编译,用户可以通过 `zig rc` 子命令来使用。 -- [Zig 多版本管理](https://zigcc.github.io/post/2023/10/14/zig-version-manager/) :: 由于 Zig 还在快速开发迭代中,因此项目很有可能出现新版本 Zig 无法编译的情况,这篇文章介绍了一些管理多个 Zig 版本的方式。 -# [项目/工具]($section.id('项目/工具')) -- [zigcli](https://zigcli.liujiacai.net/) :: a toolkit for building command lines programs in Zig -- [pb2zig](https://github.com/chung-leong/pb2zig) :: Pixel Bender to Zig code translator -- [zigar](https://github.com/chung-leong/zigar) :: Enable the use of Zig code in JavaScript project -- [jinyus/related_post_gen](https://github.com/jinyus/related_post_gen) :: 一个对常见语言进行压测的项目,项目里面有几种纯 CPU 的操作,看看哪个语言最快。 -- [nolanderc/glsl_analyzer](https://github.com/nolanderc/glsl_analyzer) :: Language server for GLSL (autocomplete, goto-definition, formatter, and more) -# [Zig 语言更新]($section.id('zig-update')) -[2023-09-01..2023-10-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-09-01..2023-10-01) +``` + +[iouring basics: Writing a file to disk](https://notes.eatonphil.com/2023-10-19-write-file-to-disk-with-io_uring.html) +作者演示了 iouring 在 Go 与 Zig +中的基本使用,下面表格是一些测试数据 + +| method | avgtime | avgthroughput | +|------------------------------|----------------------|--------------------------| +| iouring128entries | 0.2756831357s | 3.8GB/s | +| iouring1entries | 0.27575404880000004s | 3.8GB/s | +| blocking | 0.2833337046s | 3.7GB/s | + +[Zig is now also a Windows resource compiler](https://www.ryanliptak.com/blog/zig-is-a-windows-resource-compiler/) +相当硬核的文章,作者最近给 Zig 贡献了一个大功能:支持 Windows +资源定义文件的编译,用户可以通过 `zig rc` 子命令来使用。 + +[Zig 多版本管理](https://zigcc.github.io/post/2023/10/14/zig-version-manager/) +由于 Zig 还在快速开发迭代中,因此项目很有可能出现新版本 Zig +无法编译的情况,这篇文章介绍了一些管理多个 Zig 版本的方式。 + +# 项目/工具 + +[zigcli](https://zigcli.liujiacai.net/) +a toolkit for building command lines programs in Zig + +[pb2zig](https://github.com/chung-leong/pb2zig) +Pixel Bender to Zig code translator + +[zigar](https://github.com/chung-leong/zigar) +Enable the use of Zig code in JavaScript project + +[jinyus/relatedpostgen](https://github.com/jinyus/related_post_gen) +一个对常见语言进行压测的项目,项目里面有几种纯 CPU +的操作,看看哪个语言最快。 + +[nolanderc/glslanalyzer](https://github.com/nolanderc/glsl_analyzer) +Language server for GLSL (autocomplete, goto-definition, formatter, and +more) + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-09-01..2023-10-01) diff --git a/content/monthly/202311.smd b/content/monthly/202311.smd index 494f0af..686a487 100644 --- a/content/monthly/202311.smd +++ b/content/monthly/202311.smd @@ -4,14 +4,17 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2023-12-04T09:23:13+0800" }, --- -# [重大事件]($section.id('重大事件')) -本月讨论比较多的就是 [Zig May Pass Anything By Reference](https://www.1a-insec.net/blog/25-zig-reference-semantics/) 这篇文章了。 +# 重大事件 + +本月讨论比较多的就是 [Zig May Pass Anything By +Reference](https://www.1a-insec.net/blog/25-zig-reference-semantics/) +这篇文章了。 它讲述了 Zig 里面一个比较有争议的点,函数的参数到底是传值还是传引用。 -```zig + +``` zig const AAAA = struct { foo: [100]u32, }; @@ -30,56 +33,85 @@ pub fn main() !void { aaaaa(f, &f); } ``` + 上面这个例子修改了 `b` 参数的值,但是打印出来的 `a` 的值也被修改了。 -传值的好处就是不用担心原值会被修改,传引用的好处就是可以减少数据拷贝的代价。但是 Zig 目前采用的方式是由编译器推导来决定合适的方式。这样的目的是减少程序员负担,但这实际上会给程序带来不确定性,比如上述例子。 +传值的好处就是不用担心原值会被修改,传引用的好处就是可以减少数据拷贝的代价。但是 +Zig +目前采用的方式是由编译器推导来决定合适的方式。这样的目的是减少程序员负担,但这实际上会给程序带来不确定性,比如上述例子。 + +Andrew 在 +[Lobster](https://lobste.rs/s/et3ivs/zig_may_pass_anything_by_reference#c_yvfrnq) +上回复了这个问题,确实是一个比较严重的问题,一种解法是分析一个变量有多少个 +alias,编译器只在确定没问题时才进行优化,但是分析一个变量的 alias +有多少不是件容易的事。 + +其他语言如 C/C++/Rust 等没有进行这种优化尝试,因此没有这个问题,但是 Zig +作为一个新的语言,想尝试来用一种程序员无感的方式来解决,只是目前还没有想到更完善的方案而已。 + +一些熟悉 Zig zen 的读者可能会觉得这违背了第一条『Communicate intent +precisely』,目前来看确实是这样的,而且 core team +老早就意识到这个问题了,感兴趣的读者可以参考: -Andrew 在 [Lobster](https://lobste.rs/s/et3ivs/zig_may_pass_anything_by_reference#c_yvfrnq) 上回复了这个问题,确实是一个比较严重的问题,一种解法是分析一个变量有多少个 alias,编译器只在确定没问题时才进行优化,但是分析一个变量的 alias 有多少不是件容易的事。 +- [Footgun: hidden pass-by-reference + \#5973](https://github.com/ziglang/zig/issues/5973) +- [Design flaw: Swapping struct fields yields unexpected value + \#12064](https://github.com/ziglang/zig/issues/12064) -其他语言如 C/C++/Rust 等没有进行这种优化尝试,因此没有这个问题,但是 Zig 作为一个新的语言,想尝试来用一种程序员无感的方式来解决,只是目前还没有想到更完善的方案而已。 +# 观点/教程 -一些熟悉 Zig zen 的读者可能会觉得这违背了第一条『Communicate intent precisely』,目前来看确实是这样的,而且 core team 老早就意识到这个问题了,感兴趣的读者可以参考: -- [Footgun: hidden pass-by-reference #5973](https://github.com/ziglang/zig/issues/5973) -- [Design flaw: Swapping struct fields yields unexpected value #12064](https://github.com/ziglang/zig/issues/12064) -# [观点/教程]($section.id('观点/教程')) -- [Zig's std.json.Parsed(T)](https://www.openmymind.net/Zigs-std-json-Parsed/) :: 老朋友 openmymind 的文章,这篇文章主要讲述了使用 Zig 中的 json 库序列化后,如何更好的使用返回值,由于有一个 allocator 参数,因此比不能简单的返回 ~T~ ,作者这里定义了一个 ~Managed~ 来解决: - ```zig +[Zig's std.json.Parsed(T)](https://www.openmymind.net/Zigs-std-json-Parsed/) +老朋友 openmymind 的文章,这篇文章主要讲述了使用 Zig 中的 json +库序列化后,如何更好的使用返回值,由于有一个 allocator +参数,因此比不能简单的返回 `T` ,作者这里定义了一个 `Managed` 来解决: + +``` zig pub fn Managed(comptime T: type) type { - return struct { - value: T, - arena: *std.heap.ArenaAllocator, - - const Self = @This(); - - pub fn fromJson(parsed: std.json.Parsed(T)) Self { - return .{ - .arena = parsed.arena, - .value = parsed.value, - }; - } - - pub fn deinit(self: Self) void { - const arena = self.arena; - const allocator = arena.child_allocator; - arena.deinit(); - allocator.destroy(arena); - } - }; + return struct { + value: T, + arena: *std.heap.ArenaAllocator, + + const Self = @This(); + + pub fn fromJson(parsed: std.json.Parsed(T)) Self { + return .{ + .arena = parsed.arena, + .value = parsed.value, + }; + } + + pub fn deinit(self: Self) void { + const arena = self.arena; + const allocator = arena.child_allocator; + arena.deinit(); + allocator.destroy(arena); + } + }; } - ``` -- [Factor is faster than Zig!](https://re.factorcode.org/2023/11/factor-is-faster-than-zig.html) :: 一个有意思的案例分享。 -- [A day with Zig](https://www.pierrebeaucamp.com/a-day-with-zig/) :: 作者把之前一个 Go 的项目转成 Zig,这里介绍了一些感受, - - 文档缺乏 - - 文件级别导入 -- [@fieldParentPtr](https://registerspill.thorstenball.com/p/zig-zaggin) :: 对 ~@fieldParentPtr~ 使用的介绍 -- [Generating documentation from zig build](https://sudw1n.gitlab.io/posts/zig-build-docs/) :: 作者在这篇文章里尝试在 zig build 文件中输出文档,目前步骤略微繁琐。 - ```zig +``` + +[Factor is faster than Zig!](https://re.factorcode.org/2023/11/factor-is-faster-than-zig.html) +一个有意思的案例分享。 + +[A day with Zig](https://www.pierrebeaucamp.com/a-day-with-zig/) +作者把之前一个 Go 的项目转成 Zig,这里介绍了一些感受, + +- 文档缺乏 +- 文件级别导入 + +[@fieldParentPtr](https://registerspill.thorstenball.com/p/zig-zaggin) +对 `@fieldParentPtr` 使用的介绍 + +[Generating documentation from zig build](https://sudw1n.gitlab.io/posts/zig-build-docs/) +作者在这篇文章里尝试在 zig build 文件中输出文档,目前步骤略微繁琐。 + +``` zig const std = @import("std"); pub fn build(b: *std.Build) void { const exe = b.addExecutable(.{ .name = "myprogram", - .root_source_file ` .{ .path ` "src/main.zig" }, + .root_source_file = .{ .path = "src/main.zig" }, .target = b.standardTargetOptions(.{}), .optimize = b.standardOptimizeOption(.{}), }); @@ -95,15 +127,32 @@ pub fn build(b: *std.Build) void { const docs_step = b.step("docs", "Copy documentation artifacts to prefix path"); docs_step.dependOn(&install_docs.step); } - ``` -- [What's Zig got that C, Rust and Go don't have? (with Loris Cro)](https://www.youtube.com/watch?v=5_oqWE9otaE) :: [视频] Loris 参与的一档播客 -- [A Simple Example of Calling a C Library from Zig](https://mtlynch.io/notes/zig-call-c-simple/) :: 一个简明的教程,演示 Zig 如何引用 C 类库 -- [What is the Zig philosophy on APIs and abstraction?](https://www.reddit.com/r/Zig/comments/17xd46v/what_is_the_zig_philosophy_on_apis_and_abstraction/) :: 一个 Reddit 帖子,讨论 Zig 的在 API 设计上的哲学、理念。一个有意思的点是 Zig 不支持私有的字段,全部都是 public 的。Andrew 在 [Proposal: Private Fields #9909](https://github.com/ziglang/zig/issues/9909#issuecomment-942686366) 这个 issue 里面讨论过原因,主要根据: - - 一个结构体的抽象,很难保证不泄漏,比如一个类型的 align、size,一个函数是否可以在 comptime 执行 - - 一个包的兼容性,应该由文档来解释 - - 增加私有字段,会增加语言的复杂度,而且这种复杂性本身是完全可以避免的 -# [项目/工具]($section.id('项目/工具')) -- [zig build explained -- building C/C++ projects](https://zig.news/xq/zig-build-explained-part-2-1850) :: 经典文章回顾,如何使用 Zig 构建系统编译 C/C++ 项目 -- [akhildevelops/cudaz](https://github.com/akhildevelops/cudaz) :: A Zig Cuda wrapper -# [Zig 语言更新]($section.id('zig-update')) -[2023-10-01..2023-11-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-10-01..2023-11-01) +``` + +[What's Zig got that C, Rust and Go don't have? (with Loris Cro)](https://www.youtube.com/watch?v=5_oqWE9otaE) +\[视频\] Loris 参与的一档播客 + +[A Simple Example of Calling a C Library from Zig](https://mtlynch.io/notes/zig-call-c-simple/) +一个简明的教程,演示 Zig 如何引用 C 类库 + +[What is the Zig philosophy on APIs and abstraction?](https://www.reddit.com/r/Zig/comments/17xd46v/what_is_the_zig_philosophy_on_apis_and_abstraction/) +一个 Reddit 帖子,讨论 Zig 的在 API 设计上的哲学、理念。一个有意思的点是 +Zig 不支持私有的字段,全部都是 public 的。Andrew 在 [Proposal: Private +Fields +\#9909](https://github.com/ziglang/zig/issues/9909#issuecomment-942686366) +这个 issue 里面讨论过原因,主要根据: + +- 一个结构体的抽象,很难保证不泄漏,比如一个类型的 + align、size,一个函数是否可以在 comptime 执行 +- 一个包的兼容性,应该由文档来解释 +- 增加私有字段,会增加语言的复杂度,而且这种复杂性本身是完全可以避免的 + +# 项目/工具 + +[zig build explained – building C/C++ projects](https://zig.news/xq/zig-build-explained-part-2-1850) +经典文章回顾,如何使用 Zig 构建系统编译 C/C++ 项目 + +[akhildevelops/cudaz](https://github.com/akhildevelops/cudaz) +A Zig Cuda wrapper + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-10-01..2023-11-01) diff --git a/content/monthly/202402.smd b/content/monthly/202402.smd index d7425de..5c71143 100644 --- a/content/monthly/202402.smd +++ b/content/monthly/202402.smd @@ -4,42 +4,94 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2024-03-06T20:39:20+0800" }, --- -# [重大事件]($section.id('重大事件')) +# 重大事件 + Andrew 最近在 zigshow 节目中介绍了 Zig 2024 年的规划,主要有以下几点: -1. 0.12 版本会尽快发布 -2. 编译时间现在太慢,进而导致修 bug 的时间长,因此 core team 会优先解决这个编译时间问题。在这个看板中,有相应的进度,主要是:Ditch LLVM、Incremental Compilation 这两个。 - - 很多人都对 Ditch LLVM 这个事情嗤之以鼻,认为这是不自量力,[这个 issue](https://github.com/ziglang/zig/issues/16270) 的讨论也比较多,已经有近 200 条回复,最近 Andrew 增加了[一条回复](https://github.com/ziglang/zig/issues/16270#issuecomment-1905107583),引用了 [In Defense of Not-Invented-Here Syndrome](https://www.joelonsoftware.com/2001/10/14/in-defense-of-not-invented-here-syndrome/),该文章的核心观点是如果一个技术是一个产品的核心点,那么就应该自己写,因为这样才有核心竞争力,文中的例子是 Excel 团队会自己维护一个 C 编译器。 -3. 异步的支持,目前还有还有不少需要解决的技术难点,比如: - - async 函数挂起时,会保存所有上下文,但是在递归函数里,容易 oom - - 无法推倒出 函数指针 是不是 async fn 的,async fn 与普通 fn 的调用方式是不一样的 -4. Donor Bounties,捐赠性悬赏, -5. 工具链,目前还没精力,只能让社区先来做 - -更多细致总结可以参考:[Zig Roadmap 2024 - Andrew Kelley #91](https://github.com/orgs/zigcc/discussions/91) -B 站搬运地址:https://www.bilibili.com/video/BV1UC4y167w9/ -# [观点/教程]($section.id('观点/教程')) -- [Fast-growing Zig tops Stack Overflow survey for highest-paid programming language](https://www.infoworld.com/article/3713082/fast-growing-zig-tops-stack-overflow-survey-for-highest-paid-programming-language.html) :: 估计是 Bun 带动的贫富差距?! -- [Pool with generational references in Zig](https://text.garden/writings/generationalpool.html) :: 作者在文中介绍了一种有意思的思路:当有很多指针指向同一个对象时,如何不用遍历这些指针就能让其失效,答案是用一个胖指针,包括两部分:真正的数据指针和指向对象的年龄,当指针的年龄和指向对象的年龄不一致时,即认为该指针失效。 -- [TCC RISC-V Compiler runs in the Web Browser (thanks to Zig Compiler)](https://lupyuen.codeberg.page/articles/tcc.html) :: [TCC](https://github.com/sellicott/tcc-riscv32) 是一个 64 位 RISC-V 的编译器,作者在这篇文章里利用 Zig 把它编译成 WebAssembly 放到浏览器里执行。其中的难点在于 TCC 会调用 Posix 的一些函数,比如:fopen、fprintf、strncpy、malloc 等,但是这些在 WebAssembly 里是没有的,因此需要[自己实现](https://github.com/lupyuen/tcc-riscv32-wasm/blob/main/zig/tcc-wasm.zig#L447-L774),不过幸好 Zig 社区内已经有不少 libc 的实现了: - - [marler8997/ziglibc](https://github.com/marler8997/ziglibc) - - [ZigEmbeddedGroup/foundation-libc](https://github.com/ZigEmbeddedGroup/foundation-libc),A libc implementation written in Zig that is designed to be used with freestanding targets. -- [Building the DirectX shader compiler better than Microsoft?](https://devlog.hexops.com/2024/building-the-directx-shader-compiler-better-than-microsoft/) :: -- [Porting Zig to NetBSD](http://coypu.sdf.org/porting-zig.html) :: -- [Sig Engineering - Part 2 - Progress on AccountsDB & more](https://blog.syndica.io/sig-engineering-part-2-accountsdb-more/) :: Syndica 公司的 Sig,这是一款用 Zig 编写的、专注于 RPS 的 Solana 验证器客户端。在这篇文章里,他们分析了如何优化 HashMap 的性能,而且实现了一个基于本地磁盘的 Allocator。而且他们还在招聘 Zig 工程师: - - [Senior Software Engineer (Zig/C/Rust) @ Syndica](https://jobs.ashbyhq.com/syndica/15ab4e32-0f32-41a0-b8b0-16b6518158e9) - -# [项目/工具]($section.id('项目/工具')) -- [semickolon/kirei](https://github.com/semickolon/kirei) :: 🌸 The prettiest keyboard software -- [Dok8tavo/Interfacil](https://github.com/Dok8tavo/Interfacil/) :: Interfacil is a Zig package for making and using interfaces easily in Zig. -- [kamlesh-nb/azure-sdk-for-zig](https://github.com/kamlesh-nb/azure-sdk-for-zig) :: Azure Sdk for Zig - Experimental -- [ringtailsoftware/zig-wasm-audio-framebuffer](https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer) :: Examples of integrating Zig and Wasm for audio and graphics on the web -- [cztomsik/tokamak](https://github.com/cztomsik/tokamak) :: Server-side framework for Zig, relying heavily on dependency injection. -- [The-Z-Labs/cli4bofs](https://github.com/The-Z-Labs/cli4bofs) :: Command line interface for (running) BOFs -- [nelipuu/zbind](https://github.com/nelipuu/zbind) :: Zig-TypeScript binding generator 🟦 🦎 -- [sneekyfoxx/ziggy](https://github.com/sneekyfoxx/ziggy) :: 又又一个 Zig 版本管理工具 -# [Zig 语言更新]($section.id('zig-update')) -[2024-02-01..2024-03-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2024-03-01) +1. 0.12 版本会尽快发布 +2. 编译时间现在太慢,进而导致修 bug 的时间长,因此 core team + 会优先解决这个编译时间问题。在这个看板中,有相应的进度,主要是:Ditch + LLVM、Incremental Compilation 这两个。 + - 很多人都对 Ditch LLVM 这个事情嗤之以鼻,认为这是不自量力,[这个 + issue](https://github.com/ziglang/zig/issues/16270) + 的讨论也比较多,已经有近 200 条回复,最近 Andrew + 增加了[一条回复](https://github.com/ziglang/zig/issues/16270#issuecomment-1905107583),引用了 + [In Defense of Not-Invented-Here + Syndrome](https://www.joelonsoftware.com/2001/10/14/in-defense-of-not-invented-here-syndrome/),该文章的核心观点是如果一个技术是一个产品的核心点,那么就应该自己写,因为这样才有核心竞争力,文中的例子是 + Excel 团队会自己维护一个 C 编译器。 +3. 异步的支持,目前还有还有不少需要解决的技术难点,比如: + - async 函数挂起时,会保存所有上下文,但是在递归函数里,容易 oom + - 无法推倒出 函数指针 是不是 async fn 的,async fn 与普通 fn + 的调用方式是不一样的 +4. Donor Bounties,捐赠性悬赏, +5. 工具链,目前还没精力,只能让社区先来做 + +更多细致总结可以参考:[Zig Roadmap 2024 - Andrew Kelley +\#91](https://github.com/orgs/zigcc/discussions/91) B +站搬运地址:https://www.bilibili.com/video/BV1UC4y167w9/ + +# 观点/教程 + +[Fast-growing Zig tops Stack Overflow survey for highest-paid programming language](https://www.infoworld.com/article/3713082/fast-growing-zig-tops-stack-overflow-survey-for-highest-paid-programming-language.html) +估计是 Bun 带动的贫富差距?! + +[Pool with generational references in Zig](https://text.garden/writings/generationalpool.html) +作者在文中介绍了一种有意思的思路:当有很多指针指向同一个对象时,如何不用遍历这些指针就能让其失效,答案是用一个胖指针,包括两部分:真正的数据指针和指向对象的年龄,当指针的年龄和指向对象的年龄不一致时,即认为该指针失效。 + +[TCC RISC-V Compiler runs in the Web Browser (thanks to Zig Compiler)](https://lupyuen.codeberg.page/articles/tcc.html) +[TCC](https://github.com/sellicott/tcc-riscv32) 是一个 64 位 RISC-V +的编译器,作者在这篇文章里利用 Zig 把它编译成 WebAssembly +放到浏览器里执行。其中的难点在于 TCC 会调用 Posix +的一些函数,比如:fopen、fprintf、strncpy、malloc 等,但是这些在 +WebAssembly +里是没有的,因此需要[自己实现](https://github.com/lupyuen/tcc-riscv32-wasm/blob/main/zig/tcc-wasm.zig#L447-L774),不过幸好 +Zig 社区内已经有不少 libc 的实现了: + +- [marler8997/ziglibc](https://github.com/marler8997/ziglibc) +- [ZigEmbeddedGroup/foundation-libc](https://github.com/ZigEmbeddedGroup/foundation-libc),A + libc implementation written in Zig that is designed to be used with + freestanding targets. + +[Building the DirectX shader compiler better than Microsoft?](https://devlog.hexops.com/2024/building-the-directx-shader-compiler-better-than-microsoft/) + +[Porting Zig to NetBSD](http://coypu.sdf.org/porting-zig.html) + +[Sig Engineering - Part 2 - Progress on AccountsDB & more](https://blog.syndica.io/sig-engineering-part-2-accountsdb-more/) +Syndica 公司的 Sig,这是一款用 Zig 编写的、专注于 RPS 的 Solana +验证器客户端。在这篇文章里,他们分析了如何优化 HashMap +的性能,而且实现了一个基于本地磁盘的 Allocator。而且他们还在招聘 Zig +工程师: + +- [Senior Software Engineer (Zig/C/Rust) @ + Syndica](https://jobs.ashbyhq.com/syndica/15ab4e32-0f32-41a0-b8b0-16b6518158e9) + +# 项目/工具 + +[semickolon/kirei](https://github.com/semickolon/kirei) +🌸 The prettiest keyboard software + +[Dok8tavo/Interfacil](https://github.com/Dok8tavo/Interfacil/) +Interfacil is a Zig package for making and using interfaces easily in +Zig. + +[kamlesh-nb/azure-sdk-for-zig](https://github.com/kamlesh-nb/azure-sdk-for-zig) +Azure Sdk for Zig - Experimental + +[ringtailsoftware/zig-wasm-audio-framebuffer](https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer) +Examples of integrating Zig and Wasm for audio and graphics on the web + +[cztomsik/tokamak](https://github.com/cztomsik/tokamak) +Server-side framework for Zig, relying heavily on dependency injection. + +[The-Z-Labs/cli4bofs](https://github.com/The-Z-Labs/cli4bofs) +Command line interface for (running) BOFs + +[nelipuu/zbind](https://github.com/nelipuu/zbind) +Zig-TypeScript binding generator 🟦 🦎 + +[sneekyfoxx/ziggy](https://github.com/sneekyfoxx/ziggy) +又又一个 Zig 版本管理工具 + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2023-03-01) diff --git a/content/monthly/202403.smd b/content/monthly/202403.smd index 8298fd3..47aa9f1 100644 --- a/content/monthly/202403.smd +++ b/content/monthly/202403.smd @@ -4,42 +4,84 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2024-05-02T22:14:30+0800" }, --- -# [重大事件]($section.id('重大事件')) -> -https://ziglang.cc/ - -之前 ZigCC 所有项目都是托管在 GitHub 之上,网页基于 Pages 构建,域名自然也就是 github.io 的,虽然 GitHub 提供了很多利于开发者的服务,但过于依赖 GitHub 这种商业公司,还是不利于 ZigCC 的长远发展,域名是其中很重要一个,有了独立域名,网页托管选择就多了,比如 [Cloudflare Pages](https://pages.cloudflare.com/)。 - -另一个大家比较关心的问题就是 0.12 的发版,虽然 [milestone](https://github.com/ziglang/zig/milestone/23) 显示还剩 10 来个 open 的 issue,但是这只是个幌子,核心团队还是有可能随时 delay。不过从剩下的 issue 来分析,主要问题还剩两大类: -1. 构建系统完善 -2. 修复之前功能带来的回顾问题(regression 这个 tag) - -新功能看来是已经 ready 了,但这并不是说剩下的这些工具就好解决了,Andrew 在 [Zig with Andrew Kelley](https://rustacean-station.org/episode/andrew-kelley/) 这一期播客里提到的 [90-90](https://zh.wikipedia.org/wiki/90-90%E6%B3%95%E5%88%99) 理论很好的解释了这一点: -> -(开发软件时)前 90% 的代码要花费 90% 的开发时间,剩余的 10% 的代码要再花费 90% 的开发时间。 - -当然,后面 ZigCC 也会紧密关注发布动态,有消息第一时间分享给大家。耐不住寂寞的朋友,可以先去刷刷 Zig 的 discord。 -# [观点/教程]($section.id('观点/教程')) -- [Redesign How Autodoc Works](https://github.com/ziglang/zig/pull/19208) :: Andrew 在这个 PR 里重构了现有的文档系统 Autodoc,之前的实现问题很多。比如: - - 很多功能重复的文件,最夸张的是 `lib/docs/ziglexer.js` ,它是用 JS 实现的 Zig 的解析器,其实 Zig 已经在标准库中暴露解析相关 API,通过 wasm 就可以调用 - - 功能更强,因为新设计方案不再处理 ZIR,而是直接处理源文件,这意味着它拥有100% 的信息,不需要向后拼凑任何东西。 - - sources.tar 文件经 HTTP 层解压后,直接进入 wasm 模块的内存。使用 std.tar 对 tar 文件进行解析,并对源文件进行就地解析,同时在哈希表中添加一些额外的计算。虽然可以通过 Worker 来加快解析速度,但单线程的解析速度已经非常快,因此这并不是非常有必要。 - - 快来体验最新的文档系统吧:https://ziglang.org/documentation/master/std/ -- [Zig, Rust, and other languages](https://notes.eatonphil.com/2024-03-15-zig-rust-and-other-languages.html) :: 老朋友 Phil Eaton 的文章,在这里他针对以下几点进行了语言对比: - - 内存管理。Zig 最大的问题是不支持 RAII,一个近似的概念是 arenas 分配器。 - - 标准库,主要是讨论标准库是否应该精简为主, `node_modules` 是业界经常提到的一个反面例子,一般支持精简的人会认为, - - 语言的 std 不容易出现 breaking changes,想 Python 里就有 urllib、urllib2、urllib3 这三个网络库, - 但是社区推荐的并不是这三个,而是 requests,这样 std 的位置就有些尴尬 - - Zig 目前的标准库算是中等大小,json、compress 压缩等功能都有 - - 显示分配,这算是 Zig 的强项,其他语言很少有支持这个的,因此作者在这建议增加一种类似 `must-not-allocate` 的注解, - 这样高级语言里,也可以保值某些操作不会有内存分配。 -- [Why does an extraneous build step make my Zig app 10x faster?](https://mtlynch.io/zig-extraneous-build/) :: 作者在这篇文章里分享了自己遇到的一个很有意思的问题, - 同一份代码,执行方式不同,竟然有不同的耗时。最小复现代码: - ```zig +# 重大事件 + +> + +之前 ZigCC 所有项目都是托管在 GitHub 之上,网页基于 Pages +构建,域名自然也就是 github.io 的,虽然 GitHub +提供了很多利于开发者的服务,但过于依赖 GitHub 这种商业公司,还是不利于 +ZigCC +的长远发展,域名是其中很重要一个,有了独立域名,网页托管选择就多了,比如 +[Cloudflare Pages](https://pages.cloudflare.com/)。 + +另一个大家比较关心的问题就是 0.12 的发版,虽然 +[milestone](https://github.com/ziglang/zig/milestone/23) 显示还剩 10 +来个 open 的 issue,但是这只是个幌子,核心团队还是有可能随时 +delay。不过从剩下的 issue 来分析,主要问题还剩两大类: + +1. 构建系统完善 +2. 修复之前功能带来的回顾问题(regression 这个 tag) + +新功能看来是已经 ready 了,但这并不是说剩下的这些工具就好解决了,Andrew +在 [Zig with Andrew +Kelley](https://rustacean-station.org/episode/andrew-kelley/) +这一期播客里提到的 +[90-90](https://zh.wikipedia.org/wiki/90-90%E6%B3%95%E5%88%99) +理论很好的解释了这一点: + +> (开发软件时)前 90% 的代码要花费 90% 的开发时间,剩余的 10% +> 的代码要再花费 90% 的开发时间。 + +当然,后面 ZigCC +也会紧密关注发布动态,有消息第一时间分享给大家。耐不住寂寞的朋友,可以先去刷刷 +Zig 的 discord。 + +# 观点/教程 + +[Redesign How Autodoc Works](https://github.com/ziglang/zig/pull/19208) +Andrew 在这个 PR 里重构了现有的文档系统 +Autodoc,之前的实现问题很多。比如: + +- 很多功能重复的文件,最夸张的是 `lib/docs/ziglexer.js` ,它是用 JS + 实现的 Zig 的解析器,其实 Zig 已经在标准库中暴露解析相关 API,通过 + wasm 就可以调用 + +- 功能更强,因为新设计方案不再处理 + ZIR,而是直接处理源文件,这意味着它拥有100% + 的信息,不需要向后拼凑任何东西。 + +- sources.tar 文件经 HTTP 层解压后,直接进入 wasm 模块的内存。使用 + std.tar 对 tar + 文件进行解析,并对源文件进行就地解析,同时在哈希表中添加一些额外的计算。虽然可以通过 + Worker + 来加快解析速度,但单线程的解析速度已经非常快,因此这并不是非常有必要。 + + 快来体验最新的文档系统吧:https://ziglang.org/documentation/master/std/ + +[Zig, Rust, and other languages](https://notes.eatonphil.com/2024-03-15-zig-rust-and-other-languages.html) +老朋友 Phil Eaton 的文章,在这里他针对以下几点进行了语言对比: + +- 内存管理。Zig 最大的问题是不支持 RAII,一个近似的概念是 arenas + 分配器。 +- 标准库,主要是讨论标准库是否应该精简为主, `node_modules` + 是业界经常提到的一个反面例子,一般支持精简的人会认为, + - 语言的 std 不容易出现 breaking changes,想 Python 里就有 + urllib、urllib2、urllib3 这三个网络库, + 但是社区推荐的并不是这三个,而是 requests,这样 std 的位置就有些尴尬 + - Zig 目前的标准库算是中等大小,json、compress 压缩等功能都有 +- 显示分配,这算是 Zig + 的强项,其他语言很少有支持这个的,因此作者在这建议增加一种类似 + `must-not-allocate` 的注解, + 这样高级语言里,也可以保值某些操作不会有内存分配。 + +[Why does an extraneous build step make my Zig app 10x faster?](https://mtlynch.io/zig-extraneous-build/) +作者在这篇文章里分享了自己遇到的一个很有意思的问题, +同一份代码,执行方式不同,竟然有不同的耗时。最小复现代码: + +``` zig // src/main.zig const std = @import("std"); @@ -72,9 +114,11 @@ pub fn main() !void { try output.print("bytes: {}\n", .{count}); try output.print("execution time: {d:.3}µs\n", .{elapsed_micros}); } - ``` - 两种执行方式: - ```bash +``` + +两种执行方式: + +``` bash $ echo '00010203040506070809' | xxd -r -p | zig build run -Doptimize=ReleaseFast bytes: 10 execution time: 13.549µs @@ -83,21 +127,36 @@ $ echo '00010203040506070809' | xxd -r -p | ./zig-out/bin/count-bytes bytes: 10 execution time: 162.195µs ``` - 可以看到,通过 `zig build run` 的方式来执行时,耗时相比直接执行编译好的二进制要快 10 倍。 - 问题的关键在于 shell 的 pipeline 的执行机制,对于 `A | B` 这样一个简单的 pipeline,一般本能的会认为 B 只会在 A - 执行完后才开始执行,但是实际上它们是同时运行的,因此,在上面的例子里 `main` 函数的执行时间在 `zig build run` 方式下, - 其实执行的要晚一些,因为它需要先执行编译操作,因此造成了这个误差。 -- [One Bilion rows in zig](https://neurobug.com/posts/zig/billion/) :: 作者用[ 1BRC](https://1brc.dev/) 这个项目作为 Zig 的练手项目,里面用到了 [mstange/samply](https://github.com/mstange/samply) 这个 Profiler - 工具,还起来还比较实用。 -- [Zig defer Patterns](https://matklad.github.io/2024/03/21/defer-patterns.html) :: Matklad 最新的一篇文章,Ziggit [讨论链接](https://ziggit.dev/t/zig-defer-patterns/3638/3)。里面讲述了 defer 除了做资源回收外,其他的一些惯用法,里面有几个有趣的点: - ```zig + +可以看到,通过 `zig build run` +的方式来执行时,耗时相比直接执行编译好的二进制要快 10 倍。 +问题的关键在于 shell 的 pipeline 的执行机制,对于 `A | B` 这样一个简单的 +pipeline,一般本能的会认为 B 只会在 A +执行完后才开始执行,但是实际上它们是同时运行的,因此,在上面的例子里 +`main` 函数的执行时间在 `zig build run` 方式下, +其实执行的要晚一些,因为它需要先执行编译操作,因此造成了这个误差。 + +[One Bilion rows in zig](https://neurobug.com/posts/zig/billion/) +作者用[ 1BRC](https://1brc.dev/) 这个项目作为 Zig 的练手项目,里面用到了 +[mstange/samply](https://github.com/mstange/samply) 这个 Profiler +工具,还起来还比较实用。 + +[Zig defer Patterns](https://matklad.github.io/2024/03/21/defer-patterns.html) +Matklad 最新的一篇文章,Ziggit +[讨论链接](https://ziggit.dev/t/zig-defer-patterns/3638/3)。里面讲述了 +defer 除了做资源回收外,其他的一些惯用法,里面有几个有趣的点: + +``` zig errdefer comptime unreachable - ``` - 文中称这个是 Zig 的巅峰用法😅, `errdefer unreachable` 还比较好理解,即在执行出错时,执行 unreachable ,加上 comptime 呢? +``` + +文中称这个是 Zig 的巅峰用法😅, `errdefer unreachable` +还比较好理解,即在执行出错时,执行 unreachable ,加上 comptime 呢? - 其实这是阻止 Zig 编译器生产错误处理的代码,即在编译时期保证下面的逻辑不会出错,确实用的很巧妙!一个简单的例子: +其实这是阻止 Zig +编译器生产错误处理的代码,即在编译时期保证下面的逻辑不会出错,确实用的很巧妙!一个简单的例子: - ```zig +``` zig const std = @import("std"); test "errdeferWithUnreachable" { @@ -112,16 +171,25 @@ fn inc(a: i8) !i8 { } return a + 1; } - ``` - 直接执行 `zig test` ,在编译时会报下面的错误: -```bash +``` + +直接执行 `zig test` ,在编译时会报下面的错误: + +``` bash test.zig:4:23: error: reached unreachable code errdefer comptime unreachable; ``` -虽然 `a` 是个运行时的值,但是 `errdefer comptime unreachable` 不关心这个,只要 Zig 编译器开始生成 ErrorSet 相关代码, -编译就会报错,去掉上面的 if 代码块后,测试就可以正常执行。一个实际的例子: -- [std.hash_map: fix pointer lock safety false positive by andrewrk · Pull Request #19364 · ziglang/zig](https://github.com/ziglang/zig/pull/19364/files) -```diff + +虽然 `a` 是个运行时的值,但是 `errdefer comptime unreachable` +不关心这个,只要 Zig 编译器开始生成 ErrorSet 相关代码, +编译就会报错,去掉上面的 if +代码块后,测试就可以正常执行。一个实际的例子: + +- [std.hashmap: fix pointer lock safety false positive by + andrewrk · Pull Request \#19364 · + ziglang/zig](https://github.com/ziglang/zig/pull/19364/files) + +``` diff assert(std.math.isPowerOfTwo(new_cap)); var map: Self = .{}; @@ -135,7 +203,7 @@ test.zig:4:23: error: reached unreachable code @@6581,7 @@ pub fn HashMapUnmanaged( self.size = 0; - self.pointer_stability ` .{ .state ` .unlocked }; + self.pointer_stability = .{ .state = .unlocked }; std.mem.swap(Self, self, &map); + map.deinit(allocator); @@ -145,23 +213,34 @@ test.zig:4:23: error: reached unreachable code + try testing.expectError(error.OutOfMemory, map.getOrPut(std.testing.failing_allocator, "hello")); +} ``` -可以看到, 这么修改后,就可以保证 `map.deinit(allocator)` 语句之前没有错误可能产生!读者可以细细品味一下这个用法。 - 另一个小技巧是 errdefer 竟然支持错误捕获,即下面这种用法: - ```zig +可以看到, 这么修改后,就可以保证 `map.deinit(allocator)` +语句之前没有错误可能产生!读者可以细细品味一下这个用法。 + +另一个小技巧是 errdefer 竟然支持错误捕获,即下面这种用法: + +``` zig const port = port: { errdefer |err| std.log.err("failed to read the port number: {!}", .{err}); var buf: [fmt.count("{}\n", .{maxInt(u16)})]u8 = undefined; const len = try process.stdout.?.readAll(&buf); break :port try fmt.parseInt(u16, buf[0 .. len -| 1], 10); }; - ``` - - [Build system tricks](https://ziggit.dev/t/build-system-tricks/3531/1) :: 介绍了 zig build 的使用技巧,这些技巧有助于在确保方便地命名和布局构建步骤的同时,如何使用构建系统的每个部分。 - - [Using Zig with WebAssembly](https://blog.mjgrzymek.com/blog/zigwasm) :: 如何将 Zig 编译成 wasm,并传递复杂的参数。 +``` + +[Build system tricks](https://ziggit.dev/t/build-system-tricks/3531/1) +介绍了 zig build +的使用技巧,这些技巧有助于在确保方便地命名和布局构建步骤的同时,如何使用构建系统的每个部分。 + +[Using Zig with WebAssembly](https://blog.mjgrzymek.com/blog/zigwasm) +如何将 Zig 编译成 wasm,并传递复杂的参数。 -# [项目/工具]($section.id('项目/工具')) -- [xataio/pgzx](https://github.com/xataio/pgzx) :: Create PostgreSQL extensions using Zig. 一个例子: - ```zig +# 项目/工具 + +[xataio/pgzx](https://github.com/xataio/pgzx) +Create PostgreSQL extensions using Zig. 一个例子: + +``` zig const std = @import("std"); const pgzx = @import("pgzx"); @@ -188,19 +267,28 @@ fn char_count_zig(input_text: []const u8, target_char: []const u8) !u32 { } return count; } - ``` +``` -- [Manage Zig installations](https://github.com/NoelJacob/zman) :: 又又又叒一个 Zig 管理工具,Rust 开发。 - ```bash +[Manage Zig installations](https://github.com/NoelJacob/zman) +又又又叒一个 Zig 管理工具,Rust 开发。 + +``` bash zman default latest zman default master zman default 0.12.0 - ``` +``` + +[mahdifrmz/qooil](https://github.com/mahdifrmz/qooil) +用 Zig 语言编写的文件传输软件 + +[timfayz/pretty](https://github.com/timfayz/pretty) +Pretty printer for arbitrary data structures in Zig + +[liyu1981/zcmd.zig](https://github.com/liyu1981/zcmd.zig) +Zcmd is a single file lib to replace zig's std.childProcess.run with the +ability of running pipeline like bash. -- [mahdifrmz/qooil](https://github.com/mahdifrmz/qooil) :: 用 Zig 语言编写的文件传输软件 -- [timfayz/pretty](https://github.com/timfayz/pretty) :: Pretty printer for arbitrary data structures in Zig -- [liyu1981/zcmd.zig](https://github.com/liyu1981/zcmd.zig) :: Zcmd is a single file lib to replace zig's std.childProcess.run with the ability of running pipeline like bash. -- [zigcc/zig-milestone](https://github.com/zigcc/zig-milestone) :: Zig milstone monitor +[zigcc/zig-milestone](https://github.com/zigcc/zig-milestone) +Zig milstone monitor -# [Zig 语言更新]($section.id('zig-update')) -[2024-02-01..2024-03-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2024-03-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2024-03-01) diff --git a/content/monthly/202404.smd b/content/monthly/202404.smd index 030b7d2..3c477f8 100644 --- a/content/monthly/202404.smd +++ b/content/monthly/202404.smd @@ -4,29 +4,46 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2024-05-03T09:01:56+0800" }, --- -# [重大事件]($section.id('重大事件')) -千呼万唤的 0.12.0 版本终于 2024-04-20 正式释出了!这次版本历时 8 个月,有 268 位贡献者,一共进行了 3688 次提交!社区内的一些讨论:[Hacker News](https://news.ycombinator.com/item?id=40096176)、[Lobsters](https://lobste.rs/s/fa4svu)。 -这是它的 [Release notes](https://ziglang.org/download/0.12.0/release-notes.html)。ZigCC 对这个文档进行了翻译、整理,方便大家阅读: +# 重大事件 + +千呼万唤的 0.12.0 版本终于 2024-04-20 正式释出了!这次版本历时 8 +个月,有 268 位贡献者,一共进行了 3688 次提交!社区内的一些讨论:[Hacker +News](https://news.ycombinator.com/item?id=40096176)、[Lobsters](https://lobste.rs/s/fa4svu)。 +这是它的 [Release +notes](https://ziglang.org/download/0.12.0/release-notes.html)。ZigCC +对这个文档进行了翻译、整理,方便大家阅读: + - [0.12.0 升级指南](https://course.ziglang.cc/update/upgrade-0.12.0) - [0.12.0 版本说明](https://course.ziglang.cc/update/0.12.0-description) -并且还在 2024-04-27 举行了一次线上的 meetup 来庆祝这次发布,这是会议的总结:[0.12.0 Release Party 回顾](https://ziglang.cc/news/2024/04/27/release-party-review/)。 +并且还在 2024-04-27 举行了一次线上的 meetup +来庆祝这次发布,这是会议的总结:[0.12.0 Release Party +回顾](https://ziglang.cc/news/2024/04/27/release-party-review/)。 + +0.12.0 这个版本,对用户来说,最重大的变更就是构建系统的稳定了,这对于 +Zig 生态的发展是十分关键的一步,试想一个项目用到的依赖之间版本不兼容, +这是十分痛苦的事情,毫无疑问这是阻碍 Zig +生态发生的绊脚石,没有之一。好在这一切都在 0.12 +这个版本解决了,用户可以基于 Step +构成的有向无环图来编译自己的项目,不需要再折腾 +CMake、Makefile、Vcpkg、Git submodule 等工具,所有的依赖使用 zon +来管理即可。 读者如果对 Zig 构建系统还不熟悉,可以参考: -0.12.0 这个版本,对用户来说,最重大的变更就是构建系统的稳定了,这对于 Zig 生态的发展是十分关键的一步,试想一个项目用到的依赖之间版本不兼容, -这是十分痛苦的事情,毫无疑问这是阻碍 Zig 生态发生的绊脚石,没有之一。好在这一切都在 0.12 这个版本解决了,用户可以基于 Step -构成的有向无环图来编译自己的项目,不需要再折腾 CMake、Makefile、Vcpkg、Git submodule 等工具,所有的依赖使用 zon 来管理即可。 -读者如果对 Zig 构建系统还不熟悉,可以参考: - 官方文档:[Zig Build System](https://ziglang.org/learn/build-system/) -- Zig 升级: [构建系统](https://course.ziglang.cc/engineering/build-system) +- Zig 升级: + [构建系统](https://course.ziglang.cc/engineering/build-system) 期待一年后 Zig 的生态! -# [观点/教程]($section.id('观点/教程')) -- [Zig 中任意精度整数用途与实现](https://github.com/zigcc/forum/issues/112) :: 由于 CPU 在访问内存时,一般都会有对齐的要求,对于这种非常规的数字,在内存中的地址会是怎样的呢?可以做一个简单的实验: - ```zig +# 观点/教程 + +[Zig 中任意精度整数用途与实现](https://github.com/zigcc/forum/issues/112) +由于 CPU +在访问内存时,一般都会有对齐的要求,对于这种非常规的数字,在内存中的地址会是怎样的呢?可以做一个简单的实验: + +``` zig const std = @import("std"); const Foo = packed struct { @@ -44,7 +61,7 @@ pub fn main() !void { std.debug.print("Foo size: {d}\n", .{@sizeOf(Foo)}); const foos = [_]Foo{ - .{ .a ` 1, .b ` 3 }, + .{ .a = 1, .b = 3 }, }; std.debug.print("foo as bytes: {b}\n", .{std.mem.sliceAsBytes(&foos)}); @@ -53,9 +70,11 @@ pub fn main() !void { std.debug.print("{any}-{any}\n", .{ &b.a, &b.b }); } } - ``` - 输出: - ```bash +``` + +输出: + +``` bash u3@104d11a2c-1 u3@104d11a2d-10 u3@104d11a2e-11 @@ -63,21 +82,46 @@ U3 size: 1 Foo size: 1 foo as bytes: { 11001 } u3@16b196367-u2@16b196367 - ``` - - 通过前三个输出可以知道,每个 u3 实际占用一个字节,但当用在 packed 结构中,就会变成 3 个 bit。其中的 11001 就是字段 a b 混合后的值,且 a 是三位,b 是高两位。 -- [Learnings From Building a DB in Zig](https://procmarco.com/blog/learnings-from-building-a-db-in-zig/) :: 作者分享了在一次 3 天的 Hackthon 中,使用 Zig 开发一个数据库的经历。 -- [build.zig.zon dependency hashes](https://zig.news/michalsieron/buildzigzon-dependency-hashes-47kj) :: 讲解了 zon 中依赖的 hash 是怎么计算出来的 -- [play with new comptime var rule of zig 0.12.0](https://zig.news/liyu1981/play-with-new-comptime-var-rule-of-zig-0120-333k) :: -- [To SIMD and beyond: Optimizing a simple comparison routine](https://zig.news/inspectorboat/to-simd-and-beyond-optimizing-a-simple-comparison-routine-1jkf) :: 作者在这里循序渐进的介绍了几种数字比较的技巧,从基本的方案,到 Vector,到最后利用 bit 的特点,来逐步优化,并用 godbolt 查看生成的汇编代码,是一篇不错的文章。 -- [Documentation takes another step backwards : r/Zig](https://www.reddit.com/r/Zig/comments/1cc1x2v/documentation_takes_another_step_backwards/) :: 一个 Reddit 用户对文档的抱怨 -# [项目/工具]($section.id('项目/工具')) -- [rofrol/zig-companies](https://github.com/rofrol/zig-companies) :: A list of companies using Zig in production. -- [akarpovskii/tuile](https://github.com/akarpovskii/tuile) :: A Text UI library for Zig -- [mntnmntn/zenith](https://codeberg.org/mntnmntn/zenith) :: A very minimal text editor in Zig,支持 0.12.0 版本 -- [chung-leong/zigar](https://github.com/chung-leong/zigar) :: Enable the use of Zig code in JavaScript project -- [jnordwick/zig-string](https://github.com/jnordwick/zig-string) :: Zig string library that includes small string optimization on the stack -- [FalsePattern/ZigBrains](https://github.com/FalsePattern/ZigBrains) :: Yet another zig language plugin for intellij - -# [Zig 语言更新]($section.id('zig-update')) -[2024-04-01..2024-05-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-04-01..2024-05-01) +``` + +通过前三个输出可以知道,每个 u3 实际占用一个字节,但当用在 packed +结构中,就会变成 3 个 bit。其中的 11001 就是字段 a b 混合后的值,且 a +是三位,b 是高两位。 + +[Learnings From Building a DB in Zig](https://procmarco.com/blog/learnings-from-building-a-db-in-zig/) +作者分享了在一次 3 天的 Hackthon 中,使用 Zig 开发一个数据库的经历。 + +[build.zig.zon dependency hashes](https://zig.news/michalsieron/buildzigzon-dependency-hashes-47kj) +讲解了 zon 中依赖的 hash 是怎么计算出来的 + +[play with new comptime var rule of zig 0.12.0](https://zig.news/liyu1981/play-with-new-comptime-var-rule-of-zig-0120-333k) + +[To SIMD and beyond: Optimizing a simple comparison routine](https://zig.news/inspectorboat/to-simd-and-beyond-optimizing-a-simple-comparison-routine-1jkf) +作者在这里循序渐进的介绍了几种数字比较的技巧,从基本的方案,到 +Vector,到最后利用 bit 的特点,来逐步优化,并用 godbolt +查看生成的汇编代码,是一篇不错的文章。 + +[Documentation takes another step backwards : r/Zig](https://www.reddit.com/r/Zig/comments/1cc1x2v/documentation_takes_another_step_backwards/) +一个 Reddit 用户对文档的抱怨 + +# 项目/工具 + +[rofrol/zig-companies](https://github.com/rofrol/zig-companies) +A list of companies using Zig in production. + +[akarpovskii/tuile](https://github.com/akarpovskii/tuile) +A Text UI library for Zig + +[mntnmntn/zenith](https://codeberg.org/mntnmntn/zenith) +A very minimal text editor in Zig,支持 0.12.0 版本 + +[chung-leong/zigar](https://github.com/chung-leong/zigar) +Enable the use of Zig code in JavaScript project + +[jnordwick/zig-string](https://github.com/jnordwick/zig-string) +Zig string library that includes small string optimization on the stack + +[FalsePattern/ZigBrains](https://github.com/FalsePattern/ZigBrains) +Yet another zig language plugin for intellij + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-04-01..2024-05-01) diff --git a/content/monthly/202405.smd b/content/monthly/202405.smd index 790e884..ea8df91 100644 --- a/content/monthly/202405.smd +++ b/content/monthly/202405.smd @@ -4,65 +4,96 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2024-06-04T19:26:01+0800" }, --- -# [观点/教程]($section.id('观点/教程')) +# 观点/教程 + ## [Thoughts on Zig](https://arne.me/blog/thoughts-on-zig) -又一篇 Zig 初学者的使用体验文档,如果你也在犹豫要不要学 Zig,这是个不错的经验参考。 + +又一篇 Zig 初学者的使用体验文档,如果你也在犹豫要不要学 +Zig,这是个不错的经验参考。 + ## [I'm sold on Zig's simplicity : r/Zig](https://www.reddit.com/r/Zig/comments/1ckstjv/im_sold_on_zigs_simplicity/) + 一个具有资深经验开发者,在这里描述了自己选择业余项目语言的经历: - - Rust 越来越复杂,有种发展成 C++ 的趋势 - - C++ 新版本的特性(比如 module)LSP 支持的不够好,而且历史包袱严重 - - C 缺少元编程,并且没有命名空间 - - 最后从 Andrew 的一个播客了解到 Zig,经过自己尝试,发现了 Zig 没有辜负他的期望,尽管是第一次写 Zig,但基本上没有什么难度, - 每次遇到问题,仔细想几分钟就差不多有答案了。下面是他罗列的 Zig 的一些优势: - - 十分简洁,import 返回的是一个 struct,和其他变量一样使用 - - 与 C 无缝交换, - - 具有 Result 效果的错误处理 - - 唯一缺失的就是『接口』,但这一点并不是很关键,就像在 C里也没有,但是 C 可以做任何事 + +- Rust 越来越复杂,有种发展成 C++ 的趋势 +- C++ 新版本的特性(比如 module)LSP 支持的不够好,而且历史包袱严重 +- C 缺少元编程,并且没有命名空间 + +最后从 Andrew 的一个播客了解到 Zig,经过自己尝试,发现了 Zig +没有辜负他的期望,尽管是第一次写 Zig,但基本上没有什么难度, +每次遇到问题,仔细想几分钟就差不多有答案了。下面是他罗列的 Zig +的一些优势: + +- 十分简洁,import 返回的是一个 struct,和其他变量一样使用 +- 与 C 无缝交换, +- 具有 Result 效果的错误处理 +- 唯一缺失的就是『接口』,但这一点并不是很关键,就像在 C里也没有,但是 C + 可以做任何事 + # [Zig's New CLI Progress Bar Explained](https://andrewkelley.me/post/zig-new-cli-progress-bar-explained.html) -Andrew 的一篇文章,讲述了在最新版的 Zig 中,对进度条的改进实现,现在的进度展示更加友好。 + +Andrew 的一篇文章,讲述了在最新版的 Zig +中,对进度条的改进实现,现在的进度展示更加友好。 实现的难点在于在多线程环境下,如何保证高性能,文章中大致讲述了其实现: + - 首先通过预先分配好需要使用的结构,保证后续无需在进行 heap 申请 - 通过 atomic 操作来实现一个无锁的 freelist,用于申请、释放 Node + # [Writing a task scheduler in Zig](https://www.openmymind.net/Writing-a-Task-Scheduler-in-Zig/) -Openmymind 作者的又一力作,通过编写一个任务调度器,讲述了多线程编程的基本要领: + +Openmymind +作者的又一力作,通过编写一个任务调度器,讲述了多线程编程的基本要领: + - 共享的数据要加锁 + - 条件变量要和锁一起使用,会有[虚假唤醒](https://en.wikipedia.org/wiki/Spurious_wakeup)的问题,因此在被唤醒时,需要重新检查状态是否正确。 - ```zig -fn run(self: *Self) void { - while (true) { - self.mutex.lock(); - while (self.queue.peek() == null) { - self.cond.wait(&self.mutex); + + ``` zig + fn run(self: *Self) void { + while (true) { + self.mutex.lock(); + while (self.queue.peek() == null) { + self.cond.wait(&self.mutex); + } + // TODO } - // TODO } -} ``` + 它会在 wait 前释放锁,在 wait 返回时先加锁,类似下面的实现: - ```zig -fn wait(c: *std.Thread.Condition, mutex: *std.Thread.Mutex) void { - // do some setup - // ... - mutex.unlock(); + ``` zig + fn wait(c: *std.Thread.Condition, mutex: *std.Thread.Mutex) void { + // do some setup + // ... + + mutex.unlock(); - // whatever happens, we'll always return with this locked - defer mutex.lock(); + // whatever happens, we'll always return with this locked + defer mutex.lock(); - // wait for signal - // or timeout if calling timedWait - // ... -} + // wait for signal + // or timeout if calling timedWait + // ... + } ``` -# [项目/工具]($section.id('项目/工具')) -- [zigar](https://github.com/chung-leong/zigar) :: Enable the use of Zig code in JavaScript project。它可以让你直接在 JS 中调用 zig 代码,背后原理是编译成了 wasm 实现的。 -- [srijan-paul/nez](https://github.com/srijan-paul/nez) :: An emulator for the NES console. -- [deckarep/ziglang-set](https://github.com/deckarep/ziglang-set) :: A generic and general purpose Set implementation for the Zig language -- [akarpovskii/tuile](https://github.com/akarpovskii/tuile) :: A Text UI library for Zig -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page`1&q`+is%3Aclosed+is%3Apr+closed%3A2024-05-01..2024-06-01) +# 项目/工具 + +[zigar](https://github.com/chung-leong/zigar) +Enable the use of Zig code in JavaScript project。它可以让你直接在 JS +中调用 zig 代码,背后原理是编译成了 wasm 实现的。 + +[srijan-paul/nez](https://github.com/srijan-paul/nez) +An emulator for the NES console. + +[deckarep/ziglang-set](https://github.com/deckarep/ziglang-set) +A generic and general purpose Set implementation for the Zig language + +[akarpovskii/tuile](https://github.com/akarpovskii/tuile) +A Text UI library for Zig + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-05-01..2024-06-01) diff --git a/content/monthly/202406.smd b/content/monthly/202406.smd index 8cfa90b..c649c59 100644 --- a/content/monthly/202406.smd +++ b/content/monthly/202406.smd @@ -4,31 +4,40 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2024-07-01T21:51:32+0800" }, --- -# [重大事件]($section.id('重大事件')) -2024-06-07,0.13.0 发布,历时不足 2 个月,有 73 位贡献者,一共进行了 415 次提交! -这是一个相对较短的发布周期,主要原因是工具链升级,例如升级到 [LLVM 18](https://ziglang.org/download/0.13.0/release-notes.html#LLVM-18)。 +# 重大事件 -一个比较大的 Breaking changes 是 `ComptimeStringMap` 被重命名为了 `StaticStringMap` , -使用方式也发生了变化,更多细节可参考:[#19682](https://github.com/ziglang/zig/pull/19682) -```zig +2024-06-07,0.13.0 发布,历时不足 2 个月,有 73 位贡献者,一共进行了 415 +次提交! 这是一个相对较短的发布周期,主要原因是工具链升级,例如升级到 +[LLVM +18](https://ziglang.org/download/0.13.0/release-notes.html#LLVM-18)。 + +一个比较大的 Breaking changes 是 `ComptimeStringMap` 被重命名为了 +`StaticStringMap` , +使用方式也发生了变化,更多细节可参考:[\#19682](https://github.com/ziglang/zig/pull/19682) + +``` zig const map = std.StaticStringMap(T).initComptime(kvs_list); ``` -0.14.0 发布周期的主题将是编译速度。将在 0.14.0 发布周期中努力实现一些即将到来的里程碑: +0.14.0 发布周期的主题将是编译速度。将在 0.14.0 +发布周期中努力实现一些即将到来的里程碑: + - 使 x86 后端成为调试模式的默认后端。 - COFF 的链接器支持。消除对 LLVM [LLD](https://lld.llvm.org/) 的依赖。 - 启用增量编译以实现快速重建。 - 将并发引入语义分析,进一步提高编译速度。 -# [观点/教程]($section.id('观点/教程')) +# 观点/教程 + ## [Leveraging Zig's Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/) -老朋友 openmymind 的又一篇好文章:如何利用 Zig 的 Allocator 来实现请求级别的内存分配。 -// TODO -Zig Allocator 的最佳应用。这里(/post/2024/06/16/leveraging-zig-allocator/)它的中文翻译。 -```zig + +老朋友 openmymind 的又一篇好文章:如何利用 Zig 的 Allocator +来实现请求级别的内存分配。 Zig Allocator +的最佳应用。[这里](file:///post/2024/06/16/leveraging-zig-allocator/)它的中文翻译。 + +``` zig const FallbackAllocator = struct { primary: Allocator, fallback: Allocator, @@ -37,7 +46,7 @@ const FallbackAllocator = struct { pub fn allocator(self: *FallbackAllocator) Allocator { return .{ .ptr = self, - .vtable ` &.{.alloc ` alloc, .resize ` resize, .free ` free}, + .vtable = &.{.alloc = alloc, .resize = resize, .free = free}, }; } @@ -90,44 +99,73 @@ fn run(worker: *Worker) void { } } ``` + ## [On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/) -- https://news.ycombinator.com/item?id=40735667 -这篇文章作者描述了所在公司在改造老 C/C++ 项目时,为什么选择了 Zig 而不是 Rust。 -重写的项目运行在多个平台上(Web、移动端、VR 设备),因此最靠谱的方案就是暴露一个 C API,然后通过 FFI 来调用。在做决策时,重点关注以下两点: + +- + +这篇文章作者描述了所在公司在改造老 C/C++ 项目时,为什么选择了 Zig 而不是 +Rust。 重写的项目运行在多个平台上(Web、移动端、VR +设备),因此最靠谱的方案就是暴露一个 C API,然后通过 FFI +来调用。在做决策时,重点关注以下两点: + - 新语言与 C 的交互性 - 工程师扩展代码库的难易程度(如招聘和维护) 下面是 Zig VS Rust 的优势: -| | Rust | Zig | -|--------+----------------------+----------------------------------------| -| 成熟度 | 更流行、稳定;使用范围更广 | | -| 包管理 | Cargo 业界领先 | 比 Makefile 好用 | -| 安全 | 内存安全 | | -| SIMD | nightly 支持 | 通过 Vector 类型支持 | -| C 交互性 | 生态丰富 | 编译器本身就是 C 编译器,这样就可以逐步重写项目 | - -如果只是根据上面的比较,貌似还看不出选择 Zig 的动机,因此作者在最后提到: -> -Zig 大大减少了移植现有代码库和确保所有平台兼容性所需的时间和精力。我们的团队无法相信 Rust 能让这一切变得如此简单。 + +| | Rust | Zig | +|----------|----------------------------|-------------------------------------------------| +| 成熟度 | 更流行、稳定;使用范围更广 | | +| 包管理 | Cargo 业界领先 | 比 Makefile 好用 | +| 安全 | 内存安全 | | +| SIMD | nightly 支持 | 通过 Vector 类型支持 | +| C 交互性 | 生态丰富 | 编译器本身就是 C 编译器,这样就可以逐步重写项目 | + +如果只是根据上面的比较,貌似还看不出选择 Zig +的动机,因此作者在最后提到: + +> Zig +> 大大减少了移植现有代码库和确保所有平台兼容性所需的时间和精力。我们的团队无法相信 +> Rust 能让这一切变得如此简单。 相信这也是大部分人选择 Zig 的原因:简洁、高效。 + ## [Packing some Zig before going for the countryside](https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/) -作者列举的一些 Zig 学习资料、常用类库。该作者的另一篇文章也有不少资料:[2024 Collection of Zig resources](https://ludwigabap.bearblog.dev/2024-collection-of-zig-resources/) + +作者列举的一些 Zig +学习资料、常用类库。该作者的另一篇文章也有不少资料:[2024 Collection of +Zig +resources](https://ludwigabap.bearblog.dev/2024-collection-of-zig-resources/) + ## [Why I am not yet ready to switch to Zig from Rust](https://turso.tech/blog/why-i-am-not-yet-ready-to-switch-to-zig-from-rust) -Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较喜欢 C,但 C 不是一门安全的语言,因此通过 Rust,作者可以避免 -写出 SIGSEGVS 的代码,尽管 Rust 是门复杂的语言,但是因为它有完善的生态(有大公司如微软、谷歌等做背书)、已经内存安全等特点, + +Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较喜欢 C,但 C +不是一门安全的语言,因此通过 Rust,作者可以避免 写出 SIGSEGVS +的代码,尽管 Rust +是门复杂的语言,但是因为它有完善的生态(有大公司如微软、谷歌等做背书)、已经内存安全等特点, 已经是作者系统编程的首选。 -对于 Zig,尽管作者也表达了喜欢,但由于 Zig 的生态不完善,没有足够多的学习资料,因此作者觉得目前阶段选择 Zig 并不会带来 +对于 Zig,尽管作者也表达了喜欢,但由于 Zig +的生态不完善,没有足够多的学习资料,因此作者觉得目前阶段选择 Zig +并不会带来 工作上生产力的提高。这一点说的无可厚非,试想一下,如果一个项目所有的依赖都需要自己做,工作效率确实很难提上去。 -但是笔者有一点不能理解,就是该作者觉得 comptime 不好用,相比之下,他更喜欢 C 里面的宏。comptime 就是为了 C 宏的不足 -而诞生的,社区普遍也觉得 comptime 是个新颖的设计,笔者也是第一次见到这个观点,只能说,萝卜青菜,各有所爱。 +但是笔者有一点不能理解,就是该作者觉得 comptime +不好用,相比之下,他更喜欢 C 里面的宏。comptime 就是为了 C 宏的不足 +而诞生的,社区普遍也觉得 comptime +是个新颖的设计,笔者也是第一次见到这个观点,只能说,萝卜青菜,各有所爱。 + +其他社区的一些讨论:[Lobsters](https://lobste.rs/s/0mnhdx)、[Hacker +News](https://news.ycombinator.com/item?id=40681862) + +# 项目/工具 + +[malcolmstill/zware](https://github.com/malcolmstill/zware) +Zig WebAssembly Runtime Engine -其他社区的一些讨论:[Lobsters](https://lobste.rs/s/0mnhdx)、[Hacker News](https://news.ycombinator.com/item?id=40681862) -# [项目/工具]($section.id('项目/工具')) -- [malcolmstill/zware](https://github.com/malcolmstill/zware) :: Zig WebAssembly Runtime Engine -- [Cloudef/zig-aio](https://github.com/Cloudef/zig-aio) :: io_uring like asynchronous API and coroutine powered IO tasks for zig +[Cloudef/zig-aio](https://github.com/Cloudef/zig-aio) +iouring like asynchronous API and coroutine powered IO tasks +for zig -# [Zig 语言更新]($section.id('zig-update')) -[2024-06-01..2024-07-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-06-01..2024-07-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-06-01..2024-07-01) diff --git a/content/monthly/202407.smd b/content/monthly/202407.smd index 003a7c8..5890281 100644 --- a/content/monthly/202407.smd +++ b/content/monthly/202407.smd @@ -4,37 +4,56 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2024-08-01T07:18:08+0800" }, --- -# [重大事件]($section.id('重大事件')) -在[这篇文章](https://leaddev.com/tech/why-zig-one-hottest-programming-languages-learn)里,作者引用 Stackoverflow 2024 年的[调查报告](https://survey.stackoverflow.co/2024/technology),指出 Zig 语言是最热门的编程语言之一,并且 Zig 开发者的薪水都很高,平均年收入为75,332美元! +# 重大事件 -{{< figure src`"/images/stackoverflow2024-highly-desired.webp" caption`"Zig 受欢迎程度">}} +在[这篇文章](https://leaddev.com/tech/why-zig-one-hottest-programming-languages-learn)里,作者引用 +Stackoverflow 2024 +年的[调查报告](https://survey.stackoverflow.co/2024/technology),指出 +Zig 语言是最热门的编程语言之一,并且 Zig +开发者的薪水都很高,平均年收入为75,332美元! -{{< figure src`"/images/stackoverflow2024-salary.webp" caption`"Zig 薪水对比">}} -尽管使用 Zig 语言的开发者仅占调查人数的 1%,但上升趋势明显。Zig 语言的倡导者、自由和开放源码软件开发者 Ali Cheragi 说: -> -Zig 的魅力在于它的简洁性、现代设计以及在底层控制和运行时安全性之间取得的平衡。 +[Zig 受欢迎程度]($image.siteAsset('images/stackoverflow2024-salary.webp')) + +[Zig 薪水对比]($image.siteAsset('images/stackoverflow2024-salary.webp')) + 尽管使用 Zig 语言的开发者仅占调查人数的1%,但上升趋势明显。Zig 语言的倡导者、自由和开放源码软件开发者 Ali Cheragi 说: + +> Zig +> 的魅力在于它的简洁性、现代设计以及在底层控制和运行时安全性之间取得的平衡。 Zig 开发者的一些观点: -- 我选择 Zig 作为我的日常用语,是因为它独特的功能和目标组合。我被 Zig 的安全性所吸引,因为它可以让我控制最底层的部件。 -- 与许多其他语言不同,Zig 可以与现有的 C 代码实现真正的无缝互操作。出于多种原因,这一点至关重要。 -- Zig 正在对大量编程基础架构进行彻底改造,而这些基础架构在过去 40 年里无人敢碰。 - C 和 C++ 是著名的核心编程语言,在这两种语言中,你可以完全控制硬件。 但与此同时,这些语言的工具链却非常糟糕。 Zig 允许用户涉猎这些核心编程语言,但可以使用更好的工具链,兼容各种语言和更丰富的功能。 -# [观点/教程]($section.id('观点/教程')) +- 我选择 Zig 作为我的日常用语,是因为它独特的功能和目标组合。我被 Zig + 的安全性所吸引,因为它可以让我控制最底层的部件。 +- 与许多其他语言不同,Zig 可以与现有的 C + 代码实现真正的无缝互操作。出于多种原因,这一点至关重要。 +- Zig 正在对大量编程基础架构进行彻底改造,而这些基础架构在过去 40 + 年里无人敢碰。 C 和 C++ + 是著名的核心编程语言,在这两种语言中,你可以完全控制硬件。 + 但与此同时,这些语言的工具链却非常糟糕。 Zig + 允许用户涉猎这些核心编程语言,但可以使用更好的工具链,兼容各种语言和更丰富的功能。 + +# 观点/教程 + ## [Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/) - Loris Cro 的最新文章,介绍了一个改进 Zig 编码体验的小技巧,十分推荐大家使用。具体来说是这样的: - 通过配置 zls,达到保存文件时,自动进行源码检查,而且速度非常快! - ```js + +Loris Cro 的最新文章,介绍了一个改进 Zig +编码体验的小技巧,十分推荐大家使用。具体来说是这样的: 通过配置 +zls,达到保存文件时,自动进行源码检查,而且速度非常快! + +``` json { "enable_build_on_save": true, "build_on_save_step": "check" } - ``` - 将上述内存保存到 zls 的配置文件中,(路径可以通过 `zls --show-config-path` 查看 ),zls 就会在保存时,自动执行 `zig build check` ,这个 `check` 一般来说是这样的: - ```zig +``` + +将上述内存保存到 zls 的配置文件中,(路径可以通过 +`zls --show-config-path` 查看 ),zls 就会在保存时,自动执行 +`zig build check` ,这个 `check` 一般来说是这样的: + +``` zig const exe_check = b.addExecutable(.{ .name = "foo", .root_source_file = b.path("src/main.zig"), @@ -44,26 +63,41 @@ const exe_check = b.addExecutable(.{ const check = b.step("check", "Check if foo compiles"); check.dependOn(&exe_check.step); - ``` +``` + +由于 Zig 目前的一个 +bug([\#18877](https://github.com/ziglang/zig/issues/18877)),这个 +`exe_check` 不能作为 install、run 的依赖,否则在编译时,就不会增加 +`-fno-emit-bin` 选项。 而这个选项的作用就是让 Zig +来分析我们的代码,但是不会调用 LLVM +来生成最终的二进制文件,因此速度会比较快。 -由于 Zig 目前的一个 bug([#18877](https://github.com/ziglang/zig/issues/18877)),这个 `exe_check` 不能作为 install、run 的依赖,否则在编译时,就不会增加 `-fno-emit-bin` 选项。 -而这个选项的作用就是让 Zig 来分析我们的代码,但是不会调用 LLVM 来生成最终的二进制文件,因此速度会比较快。 +这个配置有个缺点,就是它是个全局配置,在 +[zigtools/zls#1687](https://github.com/zigtools/zls/issues/1687#issuecomment-1953202544) +有讨论如何改成项目级别的,本质上就是定制 zls 的启动参数。 -这个配置有个缺点,就是它是个全局配置,在 [zigtools/zls#1687](https://github.com/zigtools/zls/issues/1687#issuecomment-1953202544) 有讨论如何改成项目级别的,本质上就是定制 zls 的启动参数。 -```bash +``` bash zls --config-path zls.json ``` + 这样不同的项目就可以用不同的检查步骤了。 + ## [Systems Distributed '24](https://guergabo.substack.com/p/systems-distributed-24) + 作者对这次会议的一个回顾总结,议题主要有如下几个方向: + - Systems Thinking and Engineering Culture - The Rise of New Software Abstractions - Ensuring Safe and Correct Software - Lessons from Building Distributed Databases - Notes from Water Cooler Chats + ## [C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself](https://jstrieb.github.io/posts/c-reflection-zig/) -该作者分享了利用 typeInfo 来在编译时获取字段名的能力,要知道,在 C 里面是没有这个功能的。 -```zig + +该作者分享了利用 typeInfo 来在编译时获取字段名的能力,要知道,在 C +里面是没有这个功能的。 + +``` zig pub export fn WindowProc(hwnd: win32.HWND, uMsg: c_uint, wParam: win32.WPARAM, lParam: win32.LPARAM) callconv(windows.WINAPI) win32.LRESULT { // Handle each type of window message we care about _ = switch (uMsg) { @@ -77,8 +111,13 @@ pub export fn WindowProc(hwnd: win32.HWND, uMsg: c_uint, wParam: win32.WPARAM, l } ``` -上面这个函数是 Window 编写窗口应用时用到的回调函数,Window 操作系统会把用户触发的事件通过 `uMsg` 传递过来,为了能够从一个数字,找对对应的名字,在 Zig 里面可以用如下函数实现: -```zig + +上面这个函数是 Window 编写窗口应用时用到的回调函数,Window +操作系统会把用户触发的事件通过 `uMsg` +传递过来,为了能够从一个数字,找对对应的名字,在 Zig +里面可以用如下函数实现: + +``` zig // The WM_* macros have values less than 65536, so an array of that size can // represent all of them fn get_window_messages() [65536][:0]const u8 { @@ -95,30 +134,45 @@ fn get_window_messages() [65536][:0]const u8 { return result; } ``` + ## [A TypeScripter's Take on Zig (Advent of Code 2023)](https://effectivetypescript.com/2024/07/17/advent2023-zig/) + 以下该作者的一些心得体会: -- Zig 没有 scanf 等价物,正则表达式也不方便。因此,对于解析输入,它是拆分、拆分、拆分。最后,我分解出了一些 splitIntoBuf 和提取 IntsIntoBuf 帮助程序,这些帮助程序可以很快地读取大多数问题的输入。 -- Zig 支持所有大小的 int,一直到 u65536。如果出现溢出,请尝试使用更大的整数类型。我在一些问题上使用了 u128和 i128。 +- Zig 没有 scanf + 等价物,正则表达式也不方便。因此,对于解析输入,它是拆分、拆分、拆分。最后,我分解出了一些 + splitIntoBuf 和提取 IntsIntoBuf + 帮助程序,这些帮助程序可以很快地读取大多数问题的输入。 +- Zig 支持所有大小的 int,一直到 + u65536。如果出现溢出,请尝试使用更大的整数类型。我在一些问题上使用了 + u128和 i128。 - StringToEnum 是解析受限制的字符串或字符集的一个简单技巧。 - 可以在结构上定义一个 format 方法,使它们按照您的喜好打印。 -- 尽量避免将字符串复制到 StringHashMap 中用作键。从 JS 发出这样的命令感觉很自然,但是在 Zig 中会很尴 +- 尽量避免将字符串复制到 StringHashMap 中用作键。从 JS + 发出这样的命令感觉很自然,但是在 Zig 中会很尴 尬,因为您需要跟踪这些字符串以便稍后释放它们。如果您可以将您的键放入一个结构或元组中,那将会工作得 更好,因为它们具有值语义。如果需要字符串,可以使用切片。 -- 注意数值范围的错误。如果你想包含 max,它是 `min..(max + 1)` ,而不是 `min..max` 。 +- 注意数值范围的错误。如果你想包含 max,它是 `min..(max + 1)` ,而不是 + `min..max` 。 - 代码中将有大量的@intCast。 -- 我发现奇怪的是 Zig 有一个内置的 PriorityQueue,但是没有内置的 Queue,可以用 `std.SinglyLinkedList` 替代 -- 用于处理字符串的许多函数都在 std.mem 中,例如 std.mem.eql 和 std.mem.startsWith -- 使用 std.met.eql 比较 structs,而不是 ~=~ +- 我发现奇怪的是 Zig 有一个内置的 PriorityQueue,但是没有内置的 + Queue,可以用 `std.SinglyLinkedList` 替代 +- 用于处理字符串的许多函数都在 std.mem 中,例如 std.mem.eql 和 + std.mem.startsWith +- 使用 std.met.eql 比较 structs,而不是 `=` - 有一个按偏移量和长度切片的技巧: `array [start..][0..length]` - 记忆函数通常是很有用的。我不知道 Zig 有没有通用的方法 - 调试构建比优化构建慢得多,有时候慢10倍。如果你在一个合理的时间内得到一个答案的10倍之内,尝试一个不同的发布模式。 - 迭代时不要对数组列表进行修改 -- 在 JavaScript 允许您内联表达式的某些情况下,您可能需要分解出一个变量来澄清生存期。看看[这个问题](https://github.com/ziglang/zig/issues/12414)。 +- 在 JavaScript + 允许您内联表达式的某些情况下,您可能需要分解出一个变量来澄清生存期。看看[这个问题](https://github.com/ziglang/zig/issues/12414)。 + +# 项目/工具 + +[18alantom/fex](https://github.com/18alantom/fex) +A command-line file explorer prioritizing quick navigation. -# [项目/工具]($section.id('项目/工具')) -- [18alantom/fex](https://github.com/18alantom/fex) :: A command-line file explorer prioritizing quick navigation. -- [griush/zm](https://github.com/griush/zm) :: SIMD Math library fully cross-platform +[griush/zm](https://github.com/griush/zm) +SIMD Math library fully cross-platform -# [Zig 语言更新]($section.id('zig-update')) -[2024-07-01..2024-08-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-07-01..2024-08-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-07-01..2024-08-01) diff --git a/content/monthly/202410.smd b/content/monthly/202410.smd index e08587b..0e844c3 100644 --- a/content/monthly/202410.smd +++ b/content/monthly/202410.smd @@ -4,71 +4,131 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2024-11-03T15:21:14+0800" }, --- -# [重大事件]($section.id('重大事件')) -## [向 Zig 软件基金会认捐 30 万美元]($section.id('向 Zig 软件基金会认捐 30 万美元')) -Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金会 (ZSF) 捐赠了 300,000 美元。 -> -两年内每年分期支付15万美元。第一期已经转账。 +# 重大事件 -我从 2019 年的某个时候开始关注 Zig 项目。 我在 2021 年公开分享了我对该项目的兴奋之情。 同年晚些时候,我开始使用 Zig,到 2022 年初,我开始撰写关于 Zig 的文章,并为编译器做出贡献。 2023 年,我公开分享了用 Zig 编写的终端项目 Ghostty。 +## 向 Zig 软件基金会认捐 30 万美元 + +Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金会 (ZSF) +捐赠了 300,000 美元。 + +> 两年内每年分期支付15万美元。第一期已经转账。 + +我从 2019 年的某个时候开始关注 Zig 项目。 我在 2021 +年公开分享了我对该项目的兴奋之情。 同年晚些时候,我开始使用 Zig,到 2022 +年初,我开始撰写关于 Zig 的文章,并为编译器做出贡献。 2023 +年,我公开分享了用 Zig 编写的终端项目 Ghostty。 + +如今,我大部分的编码时间都花在了 Zig 上。 +我的家人喜欢支持我们相信的事业2。 +作为其中的一部分,我们希望支持那些我们认为可以带来变革和影响的独立软件项目,这既是回馈给我如此之多的社区的一种方式,更重要的是,这也是彰显和鼓励为热爱而构建的文化的一种方式。 +Zig 就是这样一个项目。 + +# 观点/教程 -如今,我大部分的编码时间都花在了 Zig 上。 我的家人喜欢支持我们相信的事业2。 作为其中的一部分,我们希望支持那些我们认为可以带来变革和影响的独立软件项目,这既是回馈给我如此之多的社区的一种方式,更重要的是,这也是彰显和鼓励为热爱而构建的文化的一种方式。 Zig 就是这样一个项目。 -# [观点/教程]($section.id('观点/教程')) ## [Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/) + 对 Zig 的特色进行了简单扼要的介绍,主要有: -1. UB 行为检测。 - - Zig 的指针不能是 null,需要用 optional 类型 - - C 里面的 `void*` 等价于 Zig 里面的 `?*anyopaque` 。 `void` 在 C 里面有两个意思,第一是『什么都没有』,第二是『类型不确定』,但 `void` 在 Zig 中只有第一个含义,因此用了 `anyopaque` 来表示类型擦除的指针(type-erased pointers)。 - - 数组越界检查 - - 整数溢出 -2. Bitfield, `packed struct` 可以方便的用来进行协议解析,比如对于 32 位的 RISC-V 的指令,可以这么定义解析: - ```zig -const IType = packed struct { - opcode: u7, - rd: u5, - funct3: u3, - rs1: u5, - imm: i12, // For sign-extension -}; - -const encoded_instr: u32 = 0xFFF34293; -const instr: IType = @bitCast(encoded_instr); - ``` -3. comptime,Zig 进行元编程的基础,类型是一等成员 -4. 与 C 无缝交互, `zig cc` 是交叉编译的首选 + +1. UB 行为检测。 + + - Zig 的指针不能是 null,需要用 optional 类型 + - C 里面的 `void*` 等价于 Zig 里面的 `?*anyopaque` 。 `void` 在 C + 里面有两个意思,第一是『什么都没有』,第二是『类型不确定』,但 + `void` 在 Zig 中只有第一个含义,因此用了 `anyopaque` + 来表示类型擦除的指针(type-erased pointers)。 + - 数组越界检查 + - 整数溢出 + +2. Bitfield, `packed struct` 可以方便的用来进行协议解析,比如对于 32 + 位的 RISC-V 的指令,可以这么定义解析: + + ``` zig + const IType = packed struct { + opcode: u7, + rd: u5, + funct3: u3, + rs1: u5, + imm: i12, // For sign-extension + }; + + const encoded_instr: u32 = 0xFFF34293; + const instr: IType = @bitCast(encoded_instr); + ``` + +3. comptime,Zig 进行元编程的基础,类型是一等成员 + +4. 与 C 无缝交互, `zig cc` 是交叉编译的首选 + ## [Building Nintendo 3DS Homebrew with Zig](https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html) + ## [Critical Social Infrastructure for Zig Communities | Loris Cro's Blog](https://kristoff.it/blog/critical-social-infrastructure/) + 对于一个试图共同学习如何制作大家都喜欢的软件的社区来说,能够分享想法并开展合作至关重要,但社交平台的不断起伏会导致连接中断,这对于一个从一开始就希望去中心化的社区来说是个大问题。 我有这种想法已经有一段时间了,但随着时间的推移,我们似乎越来越清楚地认识到,我们需要投资于能够长期保持可靠的交流形式,在这种交流形式中,变化是一种信号,表明社区正在发生转变(因此需要一种新的网络形态),而不是表明所选择的社交平台即将被收购/上市/加入人工智能大战。 开发者日志:迈向可靠社会基础设施的第一步 -- https://ziglang.org/devlog/ -- https://zine-ssg.io/log/ + +- +- + ## [The Zig Website Has Been Re-engineered](https://ziglang.org/news/website-zine/) + Zig 官网已经用 [Zine](https://zine-ssg.io/) 重写! + ## [Rust vs. Zig in Reality: A (Somewhat) Friendly Debate](https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/) + ## [Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun](https://kevinlynagh.com/rust-zig/) + ## [Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig](https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html) -## [视频]($section.id('视频')) -### [I made an operating system that self replicates doom on a network "from scratch"](https://www.youtube.com/watch?v`xOySJpQlmv4&feature`youtu.be) -### [Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature`shared&v`3fWx5BOiUiY) + +## 视频 + +### [I made an operating system that self replicates doom on a network “from scratch”](https://www.youtube.com/watch?v=xOySJpQlmv4&feature=youtu.be) + +### [Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature=shared&v=3fWx5BOiUiY) + ### [Let's explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764) -# [项目/工具]($section.id('项目/工具')) -- [laohanlinux/boltdb-zig](https://github.com/laohanlinux/boltdb-zig) :: a zig implement kv database -- [zigler](https://github.com/E-xyza/zigler) :: Zig NIFs in Elixir -- [gdonald/blackjack-zig](https://github.com/gdonald/blackjack-zig) :: Console Blackjack written in Zig -- [rabinnh/zig-vscode-linux](https://github.com/rabinnh/zig-vscode-linux) :: Instructions on setting up VSCode to debug Zig on Linux -- [lframosferreira/brainzuck](https://github.com/lframosferreira/brainzuck) :: [Brainf*ck](https://en.wikipedia.org/wiki/Brainfuck) interpreter written in Zig 0.12.0! Have fun! -- [BitlyTwiser/snek](https://github.com/BitlyTwiser/snek) :: A simple CLI parser to build CLI applications in Zig -- [zml/zml](https://github.com/zml/zml) :: High performance AI inference stack. Built for production. -- [BitlyTwiser/zdotenv](https://github.com/BitlyTwiser/zdotenv) :: A port of Godotenv for Zig -- [sbancuz/OpenMP-zig](https://github.com/sbancuz/OpenMP-zig) :: An implementation of the OpenMP directives for Zig -- [tusharsadhwani/zigimports](https://github.com/tusharsadhwani/zigimports) :: Automatically remove unused imports and globals from Zig files. -- [Mario-SO/zigitor](https://github.com/Mario-SO/zigitor) :: Video editor 🎬 written in Zig ⚡ using raylib -- [pwbh/ymlz](https://github.com/pwbh/ymlz) :: Small and convenient yaml parser for Zig -# [Zig 语言更新]($section.id('zig-update')) -[2024-10-01..2024-11-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-10-01..2024-11-01) + +# 项目/工具 + +[laohanlinux/boltdb-zig](https://github.com/laohanlinux/boltdb-zig) +a zig implement kv database + +[zigler](https://github.com/E-xyza/zigler) +Zig NIFs in Elixir + +[gdonald/blackjack-zig](https://github.com/gdonald/blackjack-zig) +Console Blackjack written in Zig + +[rabinnh/zig-vscode-linux](https://github.com/rabinnh/zig-vscode-linux) +Instructions on setting up VSCode to debug Zig on Linux + +[lframosferreira/brainzuck](https://github.com/lframosferreira/brainzuck) +[Brainf\*ck](https://en.wikipedia.org/wiki/Brainfuck) interpreter +written in Zig 0.12.0! Have fun! + +[BitlyTwiser/snek](https://github.com/BitlyTwiser/snek) +A simple CLI parser to build CLI applications in Zig + +[zml/zml](https://github.com/zml/zml) +High performance AI inference stack. Built for production. + +[BitlyTwiser/zdotenv](https://github.com/BitlyTwiser/zdotenv) +A port of Godotenv for Zig + +[sbancuz/OpenMP-zig](https://github.com/sbancuz/OpenMP-zig) +An implementation of the OpenMP directives for Zig + +[tusharsadhwani/zigimports](https://github.com/tusharsadhwani/zigimports) +Automatically remove unused imports and globals from Zig files. + +[Mario-SO/zigitor](https://github.com/Mario-SO/zigitor) +Video editor 🎬 written in Zig ⚡ using raylib + +[pwbh/ymlz](https://github.com/pwbh/ymlz) +Small and convenient yaml parser for Zig + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-10-01..2024-11-01) diff --git a/content/monthly/202411.smd b/content/monthly/202411.smd index 4e7d706..247f039 100644 --- a/content/monthly/202411.smd +++ b/content/monthly/202411.smd @@ -4,90 +4,151 @@ .author = "ZigCC", .layout = "monthly.shtml", .draft = false, -.custom = { .lastmod = "2024-12-09T23:27:21+0800" }, --- -# [观点/教程]($section.id('观点/教程')) +# 观点/教程 + ## [Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html) -[JAM](https://github.com/srijan-paul/jam) 作者写的一篇文章,分析里市面上现有的 JS 工具链(bundler、formatter、linter 等),虽然已经很好用,但是不够快。下面是他举的几个例子: -- Lossless, cache efficient syntax trees,现在通用的 JS 语法树表示是 [ESTree](https://github.com/estree/estree),尽管设计上很简洁,但在遍历时不够高效,需要有遍历多次 - 才能得到有用信息(eslint 里就有四次!),而且都是指针的树结构非常不利用重复利用 CPU,Carbon 编译器就有一种更紧凑的 AST 表示。 -- Compile time AST query processing。Lint 的规则大部分都是模式匹配,大部分时候都有多个嵌套的 if 逻辑,为了简化插件开发者,eslint - 采用了一种 [esquery](https://estools.github.io/esquery/) 的语法,示例: - ```js -if ( - node.type == "CallExpression" && - node.callee.type === "MemberExpression" && - node.callee.object.type === "Identifier" && - node.callee.object.name === "child_process" -) { - // many of these checks are to satisfy typescript^ -} - -if (matches( - 'CallExpression[callee.object.name = child_process]', - node - )) { - // much better -} + +[JAM](https://github.com/srijan-paul/jam) +作者写的一篇文章,分析里市面上现有的 JS +工具链(bundler、formatter、linter +等),虽然已经很好用,但是不够快。下面是他举的几个例子: + +- Lossless, cache efficient syntax trees,现在通用的 JS 语法树表示是 + [ESTree](https://github.com/estree/estree),尽管设计上很简洁,但在遍历时不够高效,需要有遍历多次 + 才能得到有用信息(eslint + 里就有四次!),而且都是指针的树结构非常不利用重复利用 CPU,Carbon + 编译器就有一种更紧凑的 AST 表示。 + +- Compile time AST query processing。Lint + 的规则大部分都是模式匹配,大部分时候都有多个嵌套的 if + 逻辑,为了简化插件开发者,eslint 采用了一种 + [esquery](https://estools.github.io/esquery/) 的语法,示例: + + ``` javascript + if ( + node.type == "CallExpression" && + node.callee.type === "MemberExpression" && + node.callee.object.type === "Identifier" && + node.callee.object.name === "child_process" + ) { + // many of these checks are to satisfy typescript^ + } + + + if (matches( + 'CallExpression[callee.object.name = child_process]', + node + )) { + // much better + } ``` - esquery 的问题在于执行效率,通过借助于 zig 的 comptime 来在编译器翻译 esquery 就避免了运行时开销。 + + esquery 的问题在于执行效率,通过借助于 zig 的 comptime 来在编译器翻译 + esquery 就避免了运行时开销。 + ## [Advent of Code in Zig | Loris Cro's Blog](https://kristoff.it/blog/advent-of-code-zig/) -一年一度的 AoC 又到了,这篇文章里给出了一些使用的技巧来帮助大家用 Zig 来解决 AoC 问题。 + +一年一度的 AoC 又到了,这篇文章里给出了一些使用的技巧来帮助大家用 Zig +来解决 AoC 问题。 + - 工具链,最新的版本 0.13 和 zls -- 手册,和 [How to read the standard library source code · ziglang/zig Wiki](https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code) + +- 手册,和 [How to read the standard library source code · ziglang/zig + Wiki](https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code) + - 使用 `embedFile` 来嵌入输入的测试用例: - ```zig -const std = @import("std"); -const input = @embedFile("path/to/input.txt"); - -pub fn main() !void { - for (input) |byte| { - //... - } -} + + ``` zig + const std = @import("std"); + const input = @embedFile("path/to/input.txt"); + + pub fn main() !void { + for (input) |byte| { + //... + } + } ``` + - 分词 + - std.mem.tokenizeScalar - std.mem.splitScalar - std.mem.splitAny - - std.mem.window 滑动窗口,用法可参考:[New way to split and iterate over strings - Zig NEWS](https://zig.news/pyrolistical/new-way-to-split-and-iterate-over-strings-2akh) + - std.mem.window 滑动窗口,用法可参考:[New way to split and iterate + over strings - Zig + NEWS](https://zig.news/pyrolistical/new-way-to-split-and-iterate-over-strings-2akh) + - 数据操作 + - 解析数字, `std.fmt.parseInt()` - - 位操作,可以用任意宽度的数字(u1,u2 等), `std.BitStack`, `std.DynamicBitSet` 这两个也非常有用 + - 位操作,可以用任意宽度的数字(u1,u2 等), `std.BitStack`, + `std.DynamicBitSet` 这两个也非常有用 更重要的,作者最后提到 AoC 可能不是学习 Zig 的最好方式: -> -虽然 AoC 非常有趣,但它并不是练习软件工程的方法。 每个 AoC 练习都要求你找到一个问题的解决方案,虽然你需要编写一个程序来解决这个问题, -但你的程序将是一个只需运行一次(一次正确)的一次性脚本。 - -当你的软件需要稳健、优化和可维护时,Zig 就会大显身手,而这些对于 AoC 来说都不重要。 -因此,请注意,虽然肯定能用 Zig 解决 AoC 问题,而且 Zig 的某些功能甚至能帮助您取得比其他语言更快的进展,但它最终还是针对软件工程进行了优化,而这并不是您在 AoC 中要做的事情。 +> 虽然 AoC 非常有趣,但它并不是练习软件工程的方法。 每个 AoC +> 练习都要求你找到一个问题的解决方案,虽然你需要编写一个程序来解决这个问题, +> 但你的程序将是一个只需运行一次(一次正确)的一次性脚本。 +> +> 当你的软件需要稳健、优化和可维护时,Zig 就会大显身手,而这些对于 AoC +> 来说都不重要。 +> +> 因此,请注意,虽然肯定能用 Zig 解决 AoC 问题,而且 Zig +> 的某些功能甚至能帮助您取得比其他语言更快的进展,但它最终还是针对软件工程进行了优化,而这并不是您在 +> AoC 中要做的事情。 ## [Zig and Emulators](https://floooh.github.io/2024/08/24/zig-and-emulators.html) + ## [Zig Reproduced Without Binaries](https://jakstys.lt/2024/zig-reproduced-without-binaries/) -一个很有趣的实验,在 0.10 版本中,Zig 编译器实现了自举,即可以用老版本的 Zig 来编译 Zig 源码,生成最新的 Zig 二进制。这里重新复习一下这个复杂的流程: -之所以复杂,问题在于老版本的 Zig 从哪里来呢?对于 Zig 来说就是 [zig1.wasm](https://github.com/ziglang/zig/blob/master/stage1/zig1.wasm),它是用没自举前的 Zig,利用 LLVM 后端,以 wasm32-wasi 为目标生成的二进制文件。 -为了保证足够小,这里面只保留了 C 后端,这样就得到了一个小到可以放到代码仓库中的 Zig 编译器。这篇文章就是证明这个文件没有被私自篡改过! +一个很有趣的实验,在 0.10 版本中,Zig 编译器实现了自举,即可以用老版本的 +Zig 来编译 Zig 源码,生成最新的 Zig +二进制。这里重新复习一下这个复杂的流程: + +之所以复杂,问题在于老版本的 Zig 从哪里来呢?对于 Zig 来说就是 +[zig1.wasm](https://github.com/ziglang/zig/blob/master/stage1/zig1.wasm),它是用没自举前的 +Zig,利用 LLVM 后端,以 wasm32-wasi 为目标生成的二进制文件。 +为了保证足够小,这里面只保留了 C +后端,这样就得到了一个小到可以放到代码仓库中的 Zig +编译器。这篇文章就是证明这个文件没有被私自篡改过! -- 之后利用 Zig 团队自己写的 wasm2c.c 把 zig1.wasm 编译成 zig1.c,之后用 cc 编译 zig1.c 就可以得到 stage1 的 zig 编译器 -- 之后再用 zig1 编译 zig 源码,由于 zig1 之后 C 后端,因此这里得到的产物是 zig2.c,再利用 cc 就可以可以 stage2 的 zig。 - zig2 功能上已经完备,但是速度很慢(没有经过 LLVM 优化) -- 最后再用 zig2 继续编译 zig 源码,得到最后的 zig3,这也是我们下载 zig 安装包时包含的版本 +- 之后利用 Zig 团队自己写的 wasm2c.c 把 zig1.wasm 编译成 zig1.c,之后用 + cc 编译 zig1.c 就可以得到 stage1 的 zig 编译器 +- 之后再用 zig1 编译 zig 源码,由于 zig1 之后 C + 后端,因此这里得到的产物是 zig2.c,再利用 cc 就可以可以 stage2 的 + zig。 zig2 功能上已经完备,但是速度很慢(没有经过 LLVM 优化) +- 最后再用 zig2 继续编译 zig 源码,得到最后的 zig3,这也是我们下载 zig + 安装包时包含的版本 - 如果再继续用 zig3 来编译 zig 源码,得到的 zig4 会和 zig3 一模一样。 细节可以参考: -- [Goodbye to the C++ Implementation of Zig](https://ziglang.org/news/goodbye-cpp/) -- [Why is the bootstrapping process so complicated? : r/Zig](https://www.reddit.com/r/Zig/comments/142gwls/why_is_the_bootstrapping_process_so_complicated/) -# [项目/工具]($section.id('项目/工具')) -- [Builds (Zig) - GoReleaser](https://goreleaser.com/customization/zig-builds/) :: 版本发布工具 GoReleaser 支持了 Zig -- [FOLLGAD/zig-ai: OpenAI SDK with streaming support](https://github.com/follgad/zig-ai) :: -- [A bunch of links to blog posts, articles, videos, etc for learning Zig](https://github.com/zouyee/zig-learning) :: -- [Super-ZIG/cli: Easy command line interface in ZIG.](https://github.com/Super-ZIG/cli) :: -- [deckarep/zigualizer](https://github.com/deckarep/zigualizer) :: Zigualizer: A music visualizer built with Zig, powered by the FFT algorithm. -- [freref/fancy-cat](https://github.com/freref/fancy-cat) :: PDF reader for terminal emulators using the Kitty image protocol -- [Dr-Nekoma/lyceum](https://github.com/Dr-Nekoma/lyceum) :: An MMO game written in Erlang (+ PostgreSQL) + Zig (+ Raylib) -# [Zig 语言更新]($section.id('zig-update')) -[2024-11-01..2024-12-01](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-11-01..2024-12-01) + +- [Goodbye to the C++ Implementation of + Zig](https://ziglang.org/news/goodbye-cpp/) +- [Why is the bootstrapping process so complicated? : + r/Zig](https://www.reddit.com/r/Zig/comments/142gwls/why_is_the_bootstrapping_process_so_complicated/) + +# 项目/工具 + +[Builds (Zig) - GoReleaser](https://goreleaser.com/customization/zig-builds/) +版本发布工具 GoReleaser 支持了 Zig + +[FOLLGAD/zig-ai: OpenAI SDK with streaming support](https://github.com/follgad/zig-ai) + +[A bunch of links to blog posts, articles, videos, etc for learning Zig](https://github.com/zouyee/zig-learning) + +[Super-ZIG/cli: Easy command line interface in ZIG.](https://github.com/Super-ZIG/cli) + +[deckarep/zigualizer](https://github.com/deckarep/zigualizer) +Zigualizer: A music visualizer built with Zig, powered by the FFT +algorithm. + +[freref/fancy-cat](https://github.com/freref/fancy-cat) +PDF reader for terminal emulators using the Kitty image protocol + +[Dr-Nekoma/lyceum](https://github.com/Dr-Nekoma/lyceum) +An MMO game written in Erlang (+ PostgreSQL) + Zig (+ Raylib) + +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-11-01..2024-12-01) diff --git a/content/monthly/index.smd b/content/monthly/index.smd index 8bdc4c1..a592285 100644 --- a/content/monthly/index.smd +++ b/content/monthly/index.smd @@ -1,9 +1,12 @@ --- .title = "月刊", -.date = @date("1990-01-01T00:00:00"), +.date = @date("2024"), .author = "ZigCC", .layout = "monthly.shtml", .draft = false, --- -社区内的最新进展,信息来源:[Zig NEWS](https://zig.news/top/month)、[Zig monthly](https://zigmonthly.org/)、[Lobsters](https://lobste.rs/t/zig)、[Reddit](https://www.reddit.com/r/Zig/)、[Zig weekly newsletter](https://discu.eu/weekly/zig/)、[Ziggit](https://ziggit.dev/)、[用户推荐](https://github.com/orgs/zigcc/discussions/new?category=%E4%BD%9C%E5%93%81%E5%88%86%E4%BA%AB) +社区内的最新进展,信息来源:[Zig NEWS](https://zig.news/top/month)、[Zig +monthly](https://zigmonthly.org/)、[Lobsters](https://lobste.rs/t/zig)、[Reddit](https://www.reddit.com/r/Zig/)、[Zig +weekly +newsletter](https://discu.eu/weekly/zig/)、[Ziggit](https://ziggit.dev/)、[用户推荐](https://github.com/orgs/zigcc/discussions/new?category=%E4%BD%9C%E5%93%81%E5%88%86%E4%BA%AB) diff --git a/content/post/2023-09-05-bog-gc-1.smd b/content/post/2023-09-05-bog-gc-1.smd index 227b82a..a06194e 100644 --- a/content/post/2023-09-05-bog-gc-1.smd +++ b/content/post/2023-09-05-bog-gc-1.smd @@ -10,6 +10,8 @@ }, --- +# Bog GC 设计 -- 概念篇 + [Bog](https://github.com/Vexu/bog) 是一款基于 Zig 开发的小型脚本语言。它的 GC 设计受到一篇论文[An efficient of Non-Moving GC for Function languages](https://www.pllab.riec.tohoku.ac.jp/papers/icfp2011UenoOhoriOtomoAuthorVersion.pdf)的启发。 ## 梗概 diff --git a/content/post/2023-09-05-hello-world.smd b/content/post/2023-09-05-hello-world.smd index 110d1c8..efc0041 100644 --- a/content/post/2023-09-05-hello-world.smd +++ b/content/post/2023-09-05-hello-world.smd @@ -13,9 +13,11 @@ # 供稿方式 +TODO + 1. Fork 仓库 https://github.com/zigcc/zigcc.github.io -2. 在 `content/post` 内添加自己的文章(md 或 org 格式均可),文件命名为: `${YYYY}-${MM}-${DD}-${SLUG}.md` -3. 文件开始需要包含一些描述信息,例如[本文件](https://github.com/zigcc/zigcc.github.io/tree/main/content/post/2023-09-05-hello-world.md)中的: +2. ~~在 `content/post` 内添加自己的文章(md 或 org 格式均可),文件命名为: `${YYYY}-${MM}-${DD}-${SLUG}.md`~~ +3. ~~文件开始需要包含一些描述信息,例如[本文件](https://github.com/zigcc/zigcc.github.io/tree/main/content/post/2023-09-05-hello-world.md)中的:~~ ``` --- diff --git a/content/post/news/2023-12-11-first-meetup.smd b/content/post/news/2023-12-11-first-meetup.smd index aa44926..fb6b87f 100644 --- a/content/post/news/2023-12-11-first-meetup.smd +++ b/content/post/news/2023-12-11-first-meetup.smd @@ -9,11 +9,11 @@ 2023 年 12 月 9 日,Zig 中文社区第一次线上会议隆重召开。共有 8 位 Zig 爱好者参加,分布在北上杭成、美国等不同地方。 -{{\< figure src="/images/first-online-meeting.webp" -caption="会议参会人员"\>}} +[会议参会人员]($image.siteAsset('images/first-online-meeting.webp')) + + +和当年的从仙童半导体出逃的人数一样,不多不少。😄 [硅谷八叛徒]($image.siteAsset('images/fair-children.webp')) -和当年的从仙童半导体出逃的人数一样,不多不少。😄 {{\< figure -src="/images/fair-children.webp" caption="硅谷八叛徒"\>}} 会议伊始,成员首先进行了个人简介,便于后续开展相应工作。随后,社区成员围绕 Zig 语言的普及进行了交流讨论。 diff --git a/convert_figure_to_image_link.js b/convert_figure_to_image_link.js new file mode 100644 index 0000000..0c41967 --- /dev/null +++ b/convert_figure_to_image_link.js @@ -0,0 +1,50 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// 递归遍历目录,处理所有 .smd 文件 +function processDirectory(dirPath) { + const items = fs.readdirSync(dirPath); + for (const item of items) { + const fullPath = path.join(dirPath, item); + const stat = fs.statSync(fullPath); + if (stat.isDirectory()) { + processDirectory(fullPath); + } else if (item.endsWith('.smd')) { + processFile(fullPath); + } + } +} + +// 处理单个文件 +function processFile(filePath) { + let content = fs.readFileSync(filePath, 'utf8'); + const newContent = replaceFigure(content); + if (newContent !== content) { + fs.writeFileSync(filePath, newContent, 'utf8'); + console.log(`✓ Converted figure in: ${filePath}`); + } +} + +// 替换 figure 块为图片链接 +function replaceFigure(content) { + // 支持 figure 块跨多行,捕获 src 路径和 caption + return content.replace(/\{\{\\<\s*figure\s+src="([^"]+)"[^>]*caption="([^"]*)"[^>]*\\>\}\}/g, + (match, src, caption) => { + // 去掉 src 开头的 / + const cleanSrc = src.replace(/^\//, ''); + return `[${caption}]($image.siteAsset('${cleanSrc}'))\n`; + } + ); +} + +// 主程序 +function main() { + const rootDir = path.join(__dirname, 'content'); + processDirectory(rootDir); +} + +if (require.main === module) { + main(); +} \ No newline at end of file diff --git a/convert_md_to_smd.js b/convert_md_to_smd.js index c9f7f88..18ea8d7 100644 --- a/convert_md_to_smd.js +++ b/convert_md_to_smd.js @@ -5,10 +5,10 @@ const path = require("path"); // 配置 const config = { - sourceDir: "./content/post/news", + sourceDir: "./content/monthly", author: "ZigCC", time: "2024", - layout: "post.shtml", + layout: "monthly.shtml", }; // 转换 markdown frontmatter 到 smd 格式 diff --git a/convert_monthly_md_to_smd.js b/convert_monthly_md_to_smd.js deleted file mode 100644 index 417b9d9..0000000 --- a/convert_monthly_md_to_smd.js +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env node - -// 复制自 convert_md_to_smd.js,仅修改 sourceDir -const fs = require('fs'); -const path = require('path'); - -// 配置 -const config = { - sourceDir: './content/monthly', - author: 'ZigCC', - time: '2024', - layout: 'monthly.shtml' -}; - -// 转换 markdown frontmatter 到 smd 格式 -function convertFrontmatter(content) { - const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/); - if (!frontmatterMatch) { - return content; - } - - const frontmatter = frontmatterMatch[1]; - const body = content.replace(/^---\n[\s\S]*?\n---\n/, ''); - - // 解析现有的 frontmatter - const lines = frontmatter.split('\n'); - const metadata = {}; - - for (const line of lines) { - const match = line.match(/^(\w+):\s*(.+)$/); - if (match) { - metadata[match[1]] = match[2].replace(/^['"]|['"]$/g, ''); // 移除引号 - } - } - - // 构建新的 smd frontmatter - const newFrontmatter = [ - '---', - `.title = "${metadata.title || 'Untitled'}",`, - `.date = @date("2024-01-01T00:00:00"),`, - `.author = "${config.author}",`, - `.layout = "${config.layout}",`, - `.draft = false,`, - '---', - '' - ].join('\n'); - - return newFrontmatter + body; -} - -// 转换 markdown 链接格式 -function convertLinks(content) { - // 转换 {{< ref "filename.md" >}} 格式到相对链接 - content = content.replace(/\{\{<\s*ref\s+"([^"]+\.md)"\s*>\}\}/g, '[$1]($1)'); - - // 可添加更多规则 - return content; -} - -// 处理单个文件 -function processFile(filePath) { - try { - const content = fs.readFileSync(filePath, 'utf8'); - const convertedContent = convertLinks(convertFrontmatter(content)); - - // 创建新的 smd 文件路径 - const dir = path.dirname(filePath); - const basename = path.basename(filePath, '.md'); - const newPath = path.join(dir, `${basename}.smd`); - - // 写入新文件 - fs.writeFileSync(newPath, convertedContent, 'utf8'); - console.log(`✓ Converted: ${filePath} -> ${newPath}`); - - return true; - } catch (error) { - console.error(`✗ Error processing ${filePath}:`, error.message); - return false; - } -} - -// 递归处理目录 -function processDirectory(dirPath) { - try { - const items = fs.readdirSync(dirPath); - let successCount = 0; - let totalCount = 0; - - for (const item of items) { - const fullPath = path.join(dirPath, item); - const stat = fs.statSync(fullPath); - - if (stat.isDirectory()) { - // 递归处理子目录 - const result = processDirectory(fullPath); - successCount += result.success; - totalCount += result.total; - } else if (item.endsWith('.md')) { - // 处理 markdown 文件 - const success = processFile(fullPath); - if (success) successCount++; - totalCount++; - } - } - - return { success: successCount, total: totalCount }; - } catch (error) { - console.error(`✗ Error processing directory ${dirPath}:`, error.message); - return { success: 0, total: 0 }; - } -} - -// 主函数 -function main() { - console.log('🚀 Starting monthly md to smd conversion...'); - console.log(`📁 Source directory: ${config.sourceDir}`); - console.log(`👤 Author: ${config.author}`); - console.log(`📅 Time: ${config.time}`); - console.log(''); - - if (!fs.existsSync(config.sourceDir)) { - console.error(`✗ Source directory does not exist: ${config.sourceDir}`); - process.exit(1); - } - - const result = processDirectory(config.sourceDir); - - console.log(''); - console.log('📊 Conversion Summary:'); - console.log(`✓ Successfully converted: ${result.success}/${result.total} files`); - - if (result.success === result.total) { - console.log('🎉 All files converted successfully!'); - } else { - console.log('⚠️ Some files failed to convert'); - } -} - -// 运行脚本 -if (require.main === module) { - main(); -} - -module.exports = { - convertFrontmatter, - convertLinks, - processFile, - processDirectory -}; \ No newline at end of file diff --git a/convert_monthly_org_to_smd.js b/convert_monthly_org_to_smd.js deleted file mode 100644 index ab6e84a..0000000 --- a/convert_monthly_org_to_smd.js +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); - -const MONTHLY_DIR = path.join(__dirname, 'content', 'monthly'); - -// 匹配 org 标题 -const orgHeadingRegex = /^(\*{1,4})\s+(.+)$/gm; -// 匹配 org 头部 -const titleRegex = /^#\+TITLE:\s*(.+)$/m; -const dateRegex = /^#\+DATE:\s*(.+)$/m; -const lastmodRegex = /^#\+LASTMOD:\s*(.+)$/m; - -// 其他 org->md 转换规则 -function orgToSmd(text) { - // 代码块 - text = text.replace(/#\+begin_src\s+(\w+)/g, '```$1'); - text = text.replace(/#\+end_src/g, '```'); - // 引用块 - text = text.replace(/#\+begin_quote/g, '>'); - text = text.replace(/#\+end_quote/g, ''); - // 行内代码 - text = text.replace(/=([^=\n]+)=/g, '`$1`'); - // 链接 [[url][desc]] - text = text.replace(/\[\[([^\]]+)\]\[([^\]]+)\]\]/g, '[$2]($1)'); - // 链接 [[url]] - text = text.replace(/\[\[([^\]]+)\]\]/g, '<$1>'); - // 图片 [[file:xxx.png]] - text = text.replace(/\[\[file:([^\]]+)\]\]/g, '![]($1)'); - // 处理 org 标题为 # [标题]($section.id('标题')) - text = text.replace(orgHeadingRegex, (match, stars, title) => { - const level = stars.length; - // 去除标题前后的空格和特殊符号 - const cleanTitle = title.trim().replace(/'/g, "\\'"); - return `${'#'.repeat(level)} [${title}]($section.id('${cleanTitle}'))`; - }); - // 去除多余 org 宏 - text = text.replace(/^#\+\w+:.*$/gm, ''); - return text; -} - -function updateFrontmatter(content, title, date, lastmod) { - // 提取 frontmatter - const fmMatch = content.match(/^---[\s\S]*?---/); - let fm = fmMatch ? fmMatch[0] : ''; - let rest = content.replace(/^---[\s\S]*?---/, '').trimStart(); - - // 更新 .title - if (title) { - fm = fm.replace(/(\.title\s*=\s*)"[^"]*"/, `$1"${title}"`); - } - // 更新 .date - if (date) { - fm = fm.replace(/(\.date\s*=\s*)@date\("[^"]*"\)/, `$1@date("${date}")`); - } - // 更新/添加 .custom - if (lastmod) { - if (/\.custom\s*=/.test(fm)) { - fm = fm.replace(/(\.custom\s*=\s*\{)[^}]*\}/, `$1 .lastmod = "${lastmod}" }`); - } else { - fm = fm.replace(/---\s*$/, `.custom = { .lastmod = "${lastmod}" },\n---`); - } - } - return fm + '\n\n' + rest; -} - -function processFile(filePath) { - let content = fs.readFileSync(filePath, 'utf8'); - // 提取 org 头部 - const titleMatch = content.match(titleRegex); - const dateMatch = content.match(dateRegex); - const lastmodMatch = content.match(lastmodRegex); - const orgTitle = titleMatch ? titleMatch[1].trim() : ''; - const orgDate = dateMatch ? dateMatch[1].trim() : ''; - const orgLastmod = lastmodMatch ? lastmodMatch[1].trim() : ''; - - // 合并 frontmatter - let newContent = updateFrontmatter( - content, - orgTitle || undefined, - orgDate || undefined, - orgLastmod || undefined - ); - // 转换正文 org->smd - newContent = orgToSmd(newContent); - // 清理多余空行 - newContent = newContent.replace(/\n{3,}/g, '\n\n'); - fs.writeFileSync(filePath, newContent, 'utf8'); - console.log('Processed:', path.basename(filePath)); -} - -function main() { - const files = fs.readdirSync(MONTHLY_DIR).filter(f => f.endsWith('.smd')); - for (const file of files) { - processFile(path.join(MONTHLY_DIR, file)); - } -} - -if (require.main === module) { - main(); -} - -module.exports = { - orgToSmd, - updateFrontmatter, - processFile -}; \ No newline at end of file diff --git a/layouts/monthly.shtml b/layouts/monthly.shtml index 939f38b..06a3d9c 100644 --- a/layouts/monthly.shtml +++ b/layouts/monthly.shtml @@ -1,4 +1,6 @@ + +
    \ No newline at end of file diff --git a/layouts/page.shtml b/layouts/page.shtml index 939f38b..06a3d9c 100644 --- a/layouts/page.shtml +++ b/layouts/page.shtml @@ -1,4 +1,6 @@ + +
    \ No newline at end of file diff --git a/layouts/post.shtml b/layouts/post.shtml index 939f38b..e898fee 100644 --- a/layouts/post.shtml +++ b/layouts/post.shtml @@ -1,4 +1,22 @@ + + + + + + + + + + +
    +

    Table of Contents

    +
    +
    \ No newline at end of file diff --git a/layouts/templates/content.shtml b/layouts/templates/content.shtml index 39aa6d6..6babbdc 100644 --- a/layouts/templates/content.shtml +++ b/layouts/templates/content.shtml @@ -1,6 +1,7 @@ +

    diff --git a/public/about/index.html b/public/about/index.html deleted file mode 100644 index d716282..0000000 --- a/public/about/index.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - -

    About Zine

    Zine is an MIT-licensed project created by Loris Cro and other contributors listed on the official repository.

    Zine is inspired by Hugo but features an entirely custom set of authoring languages:

    • Scripty is the small expression language that both SuperHTML and SuperMD share to express templating logic.

    • SuperHTML is the HTML templating language used by Zine. Unlike most {{curly braced}} templating languages, SuperHTML uses valid HTML syntax to express the templating logic, adding only minor extensions to normal HTML.
      Thanks to this approach, it offers instant syntax checking and autoformatting via a CLI tool as well as Language Server support (VScode Extension).

      NOTE

      The correct file extension for SuperHTML templates is .shtml.

    • SuperMD is a superset of Markdown that, instead of relying on inline HTML, offers new constructs for expressing content embeds without pulling into your content needless layouting concerns. A CLI tool and language server for SuperMD is in the works.

      NOTE

      The correct file extension for SuperMD pages is .smd.

    Zine is alpha software

    Zine is not yet complete. The main functionality is present and you will be able to build even moderately complex static websites without issue.

    That said using Zine today does imply participating in the development process to some degree, which usually means inquiring about the development status of a feature you need, or reporting a bug.

    Here are some quicklinks related to Zine:

    - - - - \ No newline at end of file diff --git a/public/community/index.html b/public/community/index.html deleted file mode 100644 index aace79f..0000000 --- a/public/community/index.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - -
    - - - - \ No newline at end of file diff --git a/public/contributing/index.html b/public/contributing/index.html deleted file mode 100644 index aace79f..0000000 --- a/public/contributing/index.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - -
    - - - - \ No newline at end of file diff --git a/public/highlight.css b/public/highlight.css deleted file mode 100644 index f2c4c4a..0000000 --- a/public/highlight.css +++ /dev/null @@ -1,37 +0,0 @@ -:root { - --light-yellow: #e5c07b; - --dark-yellow: #d19a66; - --blue: #61afef; - --cyan: #56b6c2; - --light-red: #e06c75; - --dark-red: #be5046; - --comment-gray: #5c6370; - --magenta: #c678dd; -} - -pre { - border-top: 1px solid white; - border-bottom: 1px solid white; - padding: 10px 5px; -} - -code.ziggy { - color: var(--cyan); -} - -code.ziggy .keyword, -code.ziggy .type { - color: var(--light-yellow); -} - -code.ziggy .string { - color: var(--dark-yellow); -} - -code.ziggy .numeric.constant { - color: var(--magenta); -} - -code.ziggy .function { - color: var(--blue); -} \ No newline at end of file diff --git a/public/index.css b/public/index.css deleted file mode 100644 index ae96e01..0000000 --- a/public/index.css +++ /dev/null @@ -1,9 +0,0 @@ -.content{ - font-size: 1.5rem; - display: flex; - flex-direction: column; - align-items: center; -} -#logo{ - width: 40vw; -} \ No newline at end of file diff --git a/public/index.html b/public/index.html deleted file mode 100644 index a3bd7e1..0000000 --- a/public/index.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - -

    Zig 中文社区致力于在中文用户中分享和传播 Zig 语言!

    我们是一群对 Zig 编程语言充满热情的开发者、学习者和爱好者。我们致力于:

    1. 分享 Zig 相关的知识和经验
    2. 翻译 Zig 官方文档和重要资源
    3. 组织线上线下的学习活动和讨论
    4. 推广 Zig 语言在中文开发者中的应用

    无论你是 Zig 专家还是初学者,我们都欢迎你的加入

    - - - - \ No newline at end of file diff --git a/public/learn/coding-in-zig/index.html b/public/learn/coding-in-zig/index.html deleted file mode 100644 index 176bcfc..0000000 --- a/public/learn/coding-in-zig/index.html +++ /dev/null @@ -1,525 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - - -
    - -

    Table of Contents

    -
      -
    • 悬空指针 Dangling Pointers
    • 所有权 Ownership
    • ArrayList
    • Anytype
    • @TypeOf
    • 构建系统
    • 第三方依赖
    -
    -

    原文地址:https://www.openmymind.net/learning_zig/coding_in_zig

    在介绍了 Zig 语言的大部分内容之后,我们将对一些主题进行回顾,并展示几种使用 Zig 编程时一些实用的技巧。在此过程中,我们将介绍更多的标准库,并介绍一些稍复杂些的代码片段。

    悬空指针 Dangling Pointers

    我们首先来看看更多关于悬空指针的例子。这似乎是一个奇怪的问题,但如果你之前主要使用带垃圾回收的语言,这可能是你学习 Zig 最大的障碍。

    你能猜到下面的输出是什么吗?

    const std = @import("std");
    -
    -pub fn main() !void {
    -	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -	const allocator = gpa.allocator();
    -
    -	var lookup = std.StringHashMap(User).init(allocator);
    -	defer lookup.deinit();
    -
    -	const goku = User{.power = 9001};
    -
    -	try lookup.put("Goku", goku);
    -
    -	// returns an optional, .? would panic if "Goku"
    -	// wasn't in our hashmap
    -	const entry = lookup.getPtr("Goku").?;
    -
    -	std.debug.print("Goku's power is: {d}\n", .{entry.power});
    -
    -	// returns true/false depending on if the item was removed
    -	_ = lookup.remove("Goku");
    -
    -	std.debug.print("Goku's power is: {d}\n", .{entry.power});
    -}
    -
    -const User = struct {
    -	power: i32,
    -};
    -
    -

    当我运行这个程序时,我得到了

    Goku's power is: 9001
    -Goku's power is: -1431655766
    -
    -

    这段代码引入了 Zig 的 std.StringHashMap,它是 std.AutoHashMap 的特定版本,键类型设置为 []const u8。即使你不能百分百确定发生了什么,也可以猜测我的输出与我们从 lookup 中删除条目后的第二次打印有关。注释掉删除的调用,输出就正常了。

    理解上述代码的关键在于了解数据在内存的中位置,或者换句话说,了解数据的所有者。请记住,Zig 参数是按值传递的,也就是说,我们传递的是值的浅副本。我们 lookup 中的 Usergoku 引用的内存不同。我们上面的代码有两个用户,每个用户都有自己的所有者。goku 的所有者是 main,而它的副本的所有者是 lookup

    getPtr 方法返回的是指向 map 中值的指针,在我们的例子中,它返回的是 *User。问题就在这里,删除会使我们的 entry指针失效。在这个示例中,getPtrremove 的位置很近,因此问题也很明显。但不难想象,代码在调用 remove 时,并不知道 entry 的引用被保存在其他地方了。

    在编写这个示例时,我并不确定会发生什么。删除有可能是通过设置内部标志来实现的,实际删除是惰性的。如果是这样的话,上面的示例在简单的情况下可能会 “奏效”,但在更复杂的情况下就会失败。这听起来非常难以调试。

    除了不调用 remove 之外,我们还可以用几种不同的方法来解决这个问题。首先,我们可以使用 get 而不是 getPtr。这样 lookup 将返回一个 User 的副本,而不再是 *User。这样我们就有了三个用户:

    1. 定义在函数内部的 gokumain 函数是其所有者
    2. 调用 lookup.put 时,形式参数会得到 goku 一个的副本,lookup 是其所有者
    3. 使用 get 函数返回的 entrymain 函数是其所有者

    由于 entry 现在是 User 的独立副本,因此将其从 lookup 中删除不会再使其失效。

    另一种方法是将 lookup 的类型从 StringHashMap(User) 改为 StringHashMap(*const User)。这段代码可以工作:

    const std = @import("std");
    -
    -pub fn main() !void {
    -	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -	const allocator = gpa.allocator();
    -
    -	// User -> *const User
    -	var lookup = std.StringHashMap(*const User).init(allocator);
    -	defer lookup.deinit();
    -
    -	const goku = User{.power = 9001};
    -
    -	// goku -> &goku
    -	try lookup.put("Goku", &goku);
    -
    -	// getPtr -> get
    -	const entry = lookup.get("Goku").?;
    -
    -	std.debug.print("Goku's power is: {d}\n", .{entry.power});
    -	_ = lookup.remove("Goku");
    -	std.debug.print("Goku's power is: {d}\n", .{entry.power});
    -}
    -
    -const User = struct {
    -	power: i32,
    -};
    -
    -

    上述代码中有许多微妙之处。首先,我们现在只有一个用户 gokulookupentry 中的值都是对 goku 的引用。我们对 remove 的调用仍然会删除 lookup 中的值,但该值只是 user 的地址,而不是 user 本身。如果我们坚持使用 getPtr,那么被 remove 后,我们就会得到一个无效的 **User。在这两种解决方案中,我们都必须使用 get 而不是 getPtr,但在这种情况下,我们只是复制地址,而不是完整的 User。对于占用内存较多的对象来说,这可能是一个很大的区别。

    如果把所有东西都放在一个函数中,再加上一个像 User 这样的小值,这仍然像是一个人为制造的问题。我们需要一个能让数据所有权成为当务之急的例子。

    所有权 Ownership

    我喜欢哈希表(HashMap),因为这是每个人都知道并且会经常使用的结构。它们有很多不同的用例,其中大部分你可能都用过。虽然哈希表可以用在一个短期查找的地方,但通常用于长期查找,因此插入其内的值需要同样长的生命周期。

    这段代码将使用终端中输入的名称来填充我们的 lookup。如果名字为空,就会停止提示循环。最后,它会检测 Leto 是否出现在 lookup 中。

    const std = @import("std");
    -const builtin = @import("builtin");
    -
    -pub fn main() !void {
    -	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -	const allocator = gpa.allocator();
    -
    -	var lookup = std.StringHashMap(User).init(allocator);
    -	defer lookup.deinit();
    -
    -	// stdin is an std.io.Reader
    -	// the opposite of an std.io.Writer, which we already saw
    -	const stdin = std.io.getStdIn().reader();
    -
    -	// stdout is an std.io.Writer
    -	const stdout = std.io.getStdOut().writer();
    -
    -	var i: i32 = 0;
    -	while (true) : (i += 1) {
    -		var buf: [30]u8 = undefined;
    -		try stdout.print("Please enter a name: ", .{});
    -		if (try stdin.readUntilDelimiterOrEof(&buf, '\n')) |line| {
    -			var name = line;
    -			// Windows平台换行以`\r\n`结束
    -			// 所以需要截取\r以获取控制台输入字符
    -			if (builtin.os.tag == .windows) {
    -			    name = @constCast(std.mem.trimRight(u8, name, "\r"));
    -			}
    -
    -			if (name.len == 0) {
    -				break;
    -			}
    -			try lookup.put(name, .{.power = i});
    -		}
    -	}
    -
    -	const has_leto = lookup.contains("Leto");
    -	std.debug.print("{any}\n", .{has_leto});
    -}
    -
    -const User = struct {
    -	power: i32,
    -};
    -
    -

    上述代码虽然区分大小写,但无论我们如何完美地输入 Letocontains 总是返回 false。让我们通过遍历 lookup 打印其值来调试一下:

    // 将这段代码放在 while 循环之后
    -
    -var it = lookup.iterator();
    -while (it.next()) |kv| {
    -	std.debug.print("{s} == {any}\n", .{kv.key_ptr.*, kv.value_ptr.*});
    -}
    -
    -
    -

    这种迭代器模式在 Zig 中很常见,它依赖于 while 和可选类型(Optional)之间的协同作用。我们的迭代器返回指向键和值的指针,因此我们用 .* 对它们进行反引用,以访问实际值而不是地址。输出结果将取决于你输入的内容,但我得到的是

    Please enter a name: Paul
    -Please enter a name: Teg
    -Please enter a name: Leto
    -Please enter a name:
    -
    -�� == learning.User{ .power = 1 }
    -
    -��� == learning.User{ .power = 0 }
    -
    -��� == learning.User{ .power = 2 }
    -false
    -
    -

    值看起来没问题,但键不一样。如果你不确定发生了什么,那可能是我的错。之前,我故意误导了你的注意力。我说哈希表通常声明周期会比较长,因此需要同等生命周期的值(value)。事实上,哈希表不仅需要长生命周期的值,还需要长生命周期的键(key)!请注意,buf 是在 while 循环中定义的。当我们调用 put 时,我们给了哈希表插入一个键值对,这个键的生命周期比哈希表本身短得多。将 buf 移到 while 循环之外可以解决生命周期问题,但每次迭代都会重复使用缓冲区。由于我们正在更改底层的键数据,因此它仍然无法工作。

    对于上述代码,实际上只有一种解决方案:我们的 lookup 必须拥有键的所有权。我们需要添加一行并修改另一行:

    // 用这两行替换现有的 lookup.put
    -const owned_name = try allocator.dupe(u8, name);
    -
    -// name -> owned_name
    -try lookup.put(owned_name, .{.power = i});
    -
    -

    dupestd.mem.Allocator 中的一个方法,我们以前从未见过。它会分配给定值的副本。代码现在可以工作了,因为我们的键现在在堆上,比 lookup的生命周期更长。事实上,我们在延长这些字符串的生命周期方面做得太好了,以至于引入了内存泄漏。

    你可能以为当我们调用 lookup.deinit 时,键和值就会被释放。但 StringHashMap 并没有放之四海而皆准的解决方案。首先,键可能是字符串文字,无法释放。其次,它们可能是用不同的分配器创建的。最后,虽然更先进,但在某些情况下,键可能不属于哈希表。

    唯一的解决办法就是自己释放键值。在这一点上,创建我们自己的 UserLookup 类型并在 deinit 函数中封装这一清理逻辑可能会比较合理。一种简单的改法:

    // 用以下的代码替换现有的 defer lookup.deinit();
    -defer {
    -	var it = lookup.keyIterator();
    -	while (it.next()) |key| {
    -		allocator.free(key.*);
    -	}
    -	lookup.deinit();
    -}
    -
    -

    这里的 defer 逻辑使用了一个代码快,它释放每个键,最后去释放 lookup 本身。我们使用的 keyIterator 只会遍历键。迭代器的值是指向哈希映射中键的指针,即 *[]const u8。我们希望释放实际的值,因为这是我们通过 dupe 分配的,所以我们使用 key.*.

    我保证,关于悬挂指针和内存管理的讨论已经结束了。我们所讨论的内容可能还不够清晰或过于抽象。当你有更实际的问题需要解决时,再重新讨论这个问题也不迟。不过,如果你打算编写任何稍具规模(non-trivial)的程序,这几乎肯定是你需要掌握的内容。当你觉得可以的时候,我建议你参考上面这个示例,并自己动手实践一下。引入一个 UserLookup 类型来封装我们必须做的所有内存管理。尝试使用 *User 代替 User,在堆上创建用户,然后像处理键那样释放它们。编写覆盖新结构的测试,使用 std.testing.allocator 确保不会泄漏任何内存。

    ArrayList

    现在你可以忘掉我们的 IntList 和我们创建的通用替代方案了。Zig 标准库中有一个动态数组实现:std.ArrayList(T)

    它是相当标准的东西,但由于它如此普遍需要和使用的数据结构,值得看看它的实际应用:

    const std = @import("std");
    -const Allocator = std.mem.Allocator;
    -const builtin = @import("builtin");
    -
    -pub fn main() !void {
    -	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -	const allocator = gpa.allocator();
    -
    -	var arr = std.ArrayList(User).init(allocator);
    -	defer {
    -		for (arr.items) |user| {
    -			user.deinit(allocator);
    -		}
    -		arr.deinit();
    -	}
    -
    -	// stdin is an std.io.Reader
    -	// the opposite of an std.io.Writer, which we already saw
    -	const stdin = std.io.getStdIn().reader();
    -
    -	// stdout is an std.io.Writer
    -	const stdout = std.io.getStdOut().writer();
    -
    -	var i: i32 = 0;
    -	while (true) : (i += 1) {
    -		var buf: [30]u8 = undefined;
    -		try stdout.print("Please enter a name: ", .{});
    -		if (try stdin.readUntilDelimiterOrEof(&buf, '\n')) |line| {
    -			var name = line;
    -			if (builtin.os.tag == .windows) {
    -				name = @constCast(std.mem.trimRight(u8, name, "\r"));
    -			}
    -
    -			if (name.len == 0) {
    -				break;
    -			}
    -			const owned_name = try allocator.dupe(u8, name);
    -			try arr.append(.{.name = owned_name, .power = i});
    -		}
    -	}
    -
    -	var has_leto = false;
    -	for (arr.items) |user| {
    -		if (std.mem.eql(u8, "Leto", user.name)) {
    -			has_leto = true;
    -			break;
    -		}
    -	}
    -
    -	std.debug.print("{any}\n", .{has_leto});
    -}
    -
    -const User = struct {
    -	name: []const u8,
    -	power: i32,
    -
    -	fn deinit(self: User, allocator: Allocator) void {
    -		allocator.free(self.name);
    -	}
    -};
    -
    -

    以上是哈希表代码的基于 ArrayList(User) 的另一种实现。所有相同的生命周期和内存管理规则都适用。请注意,我们仍在创建 name 的副本,并且仍在删除 ArrayList 之前释放每个 name

    现在是指出 Zig 没有属性或私有字段的好时机。当我们访问 arr.items 来遍历值时,就可以看到这一点。没有属性的原因是为了消除阅读 Zig 代码中的歧义。在 Zig 中,如果看起来像字段访问,那就是字段访问。我个人认为,没有私有字段是一个错误,但我们可以解决这个问题。我已经习惯在字段前加上下划线,表示『仅供内部使用』。

    由于字符串的类型是 []u8[]const u8,因此 ArrayList(u8) 是字符串构造器的合适类型,比如 .NET 的 StringBuilder 或 Go 的 strings.Builder。事实上,当一个函数的参数是 Writer 而你需要一个字符串时,就会用到 ArrayList(u8)。我们之前看过一个使用 std.json.stringify 将 JSON 输出到 stdout 的示例。下面是将 JSON 输出到 ArrayList(u8) 的示例:

    const std = @import("std");
    -
    -pub fn main() !void {
    -	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -	const allocator = gpa.allocator();
    -
    -	var out = std.ArrayList(u8).init(allocator);
    -	defer out.deinit();
    -
    -	try std.json.stringify(.{
    -		.this_is = "an anonymous struct",
    -		.above = true,
    -		.last_param = "are options",
    -	}, .{.whitespace = .indent_2}, out.writer());
    -
    -	std.debug.print("{s}\n", .{out.items});
    -}
    -
    -

    Anytype

    语言概述的第一部分中,我们简要介绍了 anytype。这是一种非常有用的编译时 duck 类型。下面是一个简单的 logger:

    pub const Logger = struct {
    -	level: Level,
    -
    -	// "error" is reserved, names inside an @"..." are always
    -	// treated as identifiers
    -	const Level = enum {
    -		debug,
    -		info,
    -		@"error",
    -		fatal,
    -	};
    -
    -	fn info(logger: Logger, msg: []const u8, out: anytype) !void {
    -		if (@intFromEnum(logger.level) <= @intFromEnum(Level.info)) {
    -			try out.writeAll(msg);
    -		}
    -	}
    -};
    -
    -

    info 函数的 out 参数类型为 anytype。这意味着我们的 logger 可以将信息输出到任何具有 writeAll 方法的结构中,该方法接受一个 []const u8 并返回一个 !void。这不是运行时特性。类型检查在编译时进行,每使用一种类型,就会创建一个类型正确的函数。如果我们试图调用 info,而该类型不具备所有必要的函数(本例中只有 writeAll),我们就会在编译时出错:

    var l = Logger{.level = .info};
    -try l.info("sever started", true);
    -
    -

    会得到如下错误:

    no field or member function named 'writeAll' in 'bool'
    -
    -

    使用 ArrayList(u8)writer 就可以运行:

    pub fn main() !void {
    -	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -	const allocator = gpa.allocator();
    -
    -	var l = Logger{.level = .info};
    -
    -	var arr = std.ArrayList(u8).init(allocator);
    -	defer arr.deinit();
    -
    -	try l.info("sever started", arr.writer());
    -	std.debug.print("{s}\n", .{arr.items});
    -}
    -
    -

    anytype 的一个最大缺点就是文档。下面是我们用过几次的 std.json.stringify 函数的签名:

    // 我**讨厌**多行函数定义
    -// 不过,鉴于你可能在小屏幕上阅读这个指南,因此这里破一次例。
    -
    -fn stringify(
    -	value: anytype,
    -	options: StringifyOptions,
    -	out_stream: anytype
    -) @TypeOf(out_stream).Error!void
    -
    -

    第一个参数 value: anytype 是显而易见的,它是要序列化的值,可以是任何类型(实际上,Zig 的 JSON 序列化器不能序列化某些类型,比如 HashMap)。我们可以猜测,out_stream 是写入 JSON 的地方,但至于它需要实现什么方法,你和我一样猜得到。唯一的办法就是阅读源代码,或者传递一个假值,然后使用编译器错误作为我们的文档。如果有更好的自动文档生成器,这一点可能会得到改善。不过,我希望 Zig 能提供接口,这已经不是第一次了。

    @TypeOf

    在前面的部分中,我们使用 @TypeOf 来帮助我们检查各种变量的类型。从我们的用法来看,你可能会认为它返回的是字符串类型的名称。然而,鉴于它是一个 PascalCase 风格函数,你应该更清楚:它返回的是一个 type

    我最喜欢用 anytype@TypeOf@hasField 内置函数搭配使用,以编写测试帮助程序。虽然我们看到的每个 User 类型都非常简单,但我还是要请大家想象一下一个有很多字段的更复杂的结构。在许多测试中,我们需要一个 User,但我们只想指定与测试相关的字段。让我们创建一个 userFactory

    fn userFactory(data: anytype) User {
    -	const T = @TypeOf(data);
    -	return .{
    -		.id = if (@hasField(T, "id")) data.id else 0,
    -		.power = if (@hasField(T, "power")) data.power else 0,
    -		.active  = if (@hasField(T, "active")) data.active else true,
    -		.name  = if (@hasField(T, "name")) data.name else "",
    -	};
    -}
    -
    -pub const User = struct {
    -	id: u64,
    -	power: u64,
    -	active: bool,
    -	name: [] const u8,
    -};
    -
    -

    我们可以通过调用 userFactory(.{}) 创建默认用户,也可以通过 userFactory(.{.id = 100, .active = false}) 来覆盖特定字段。这只是一个很小的模式,但我非常喜欢。这也是迈向元编程世界的第一步。

    更常见的是 @TypeOf@typeInfo 配对,后者返回一个 std.builtin.Type。这是一个功能强大的带标签的联合(tagged union),可以完整描述一个类型。std.json.stringify 函数会递归地调用它,以确定如何将提供的 value 序列化。

    构建系统

    如果你通读了整本指南,等待着深入了解如何建立更复杂的项目,包括多个依赖关系和各种目标,那你就要失望了。Zig 拥有强大的构建系统,以至于越来越多的非 Zig 项目都在使用它,比如 libsodium。不幸的是,所有这些强大的功能都意味着,对于简单的需求来说,它并不是最容易使用或理解的。

    事实上,是我不太了解 Zig 的构建系统,所以无法解释清楚。

    不过,我们至少可以获得一个简要的概述。为了运行 Zig 代码,我们使用了 zig run learning.zig。有一次,我们还用 zig test learning.zig 进行了一次测试。运行和测试命令用来玩玩还行,但如果要做更复杂的事情,就需要使用构建命令了。编译命令依赖于带有特殊编译入口的 build.zig 文件。下面是一个示例:

    // build.zig
    -
    -const std = @import("std");
    -
    -pub fn build(b: *std.Build) !void {
    -	_ = b;
    -}
    -
    -

    每个构建程序都有一个默认的『安装』步骤,可以使用 zig build install 运行它,但由于我们的文件大部分是空的,你不会得到任何有意义的工件。我们需要告诉构建程序我们程序的入口是 learning.zig

    const std = @import("std");
    -
    -pub fn build(b: *std.Build) !void {
    -	const target = b.standardTargetOptions(.{});
    -	const optimize = b.standardOptimizeOption(.{});
    -
    -	// setup executable
    -	const exe = b.addExecutable(.{
    -		.name = "learning",
    -		.target = target,
    -		.optimize = optimize,
    -		.root_source_file = b.path("learning.zig"),
    -	});
    -	b.installArtifact(exe);
    -}
    -
    -

    现在,如果运行 zig build install,就会在 ./zig-out/bin/learning 中得到一个二进制文件。通过使用 standardTargetOptionsstandardOptimizeOption,我们就能以命令行参数的形式覆盖默认值。例如,要为 Windows 构建一个大小优化的程序版本,我们可以这样做:

    zig build install -Doptimize=ReleaseSmall -Dtarget=x86_64-windows-gnu
    -
    -

    除了默认的『安装』步骤外,可执行文件通常还会增加两个步骤:『运行』和『测试』。一个库可能只有一个『测试』步骤。对于基本的无参数即可运行的程序来说,只需要在构建文件的最后添加四行:

    // 在这行代码后添加下面的代码: b.installArtifact(exe);
    -
    -const run_cmd = b.addRunArtifact(exe);
    -run_cmd.step.dependOn(b.getInstallStep());
    -
    -const run_step = b.step("run", "Start learning!");
    -run_step.dependOn(&run_cmd.step);
    -
    -

    这里通过 dependOn 的两次调用创建两个依赖关系。第一个依赖关系将我们的 run_cmd 与内置的安装步骤联系起来。第二个是将 run_step 与我们新创建的 run_cmd 绑定。你可能想知道为什么需要 run_cmdrun_step。我认为这种分离是为了支持更复杂的设置:依赖于多个命令的步骤,或者在多个步骤中使用的命令。如果运行 zig build --help 并滚动到顶部,你会看到新增的 run 步骤。现在你可以执行 zig build run 来运行程序了。

    要添加『测试』步骤,你需要重复刚才添加的大部分运行代码,只是不再使用 b.addExecutable,而是使用 b.addTest

    const tests = b.addTest(.{
    -	.target = target,
    -	.optimize = optimize,
    -	.root_source_file = b.path("learning.zig"),
    -});
    -
    -const test_cmd = b.addRunArtifact(tests);
    -test_cmd.step.dependOn(b.getInstallStep());
    -const test_step = b.step("test", "Run the tests");
    -test_step.dependOn(&test_cmd.step);
    -
    -

    我们将该步骤命名为 test。运行 zig build --help 会显示另一个可用步骤 test。由于我们没有进行任何测试,因此很难判断这一步是否有效。在 learning.zig 中,添加

    test "dummy build test" {
    -	try std.testing.expectEqual(false, true);
    -}
    -
    -

    现在运行 zig build test时,应该会出现测试失败。如果你修复了测试,并再次运行 zig build test,你将不会得到任何输出。默认情况下,Zig 的测试运行程序只在失败时输出结果。如果你像我一样,无论成功还是失败,都想要一份总结,那就使用 zig build test --summary all

    这是启动和运行构建系统所需的最低配置。但是请放心,如果你需要构建你的程序,Zig 内置的功能大概率能覆盖你的需求。最后,你可以(也应该)在你的项目根目录下使用 zig init,让 Zig 为你创建一个文档齐全的 build.zig 文件。

    第三方依赖

    Zig 的内置软件包管理器相对较新,因此存在一些缺陷。虽然还有改进的余地,但它目前还是可用的。我们需要了解两个部分:创建软件包和使用软件包。我们将对其进行全面介绍。

    首先,新建一个名为 calc 的文件夹并创建三个文件。第一个是 add.zig,内容如下:

    // 哦,下面的函数定义中有语法之前没讲过,看看 b 的类型和返回类型!!
    -
    -pub fn add(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
    -	return a + b;
    -}
    -
    -const testing = @import("std").testing;
    -test "add" {
    -	try testing.expectEqual(@as(i32, 32), add(30, 2));
    -}
    -
    -

    这个例子可能看起来有点傻,一整个软件包只是为了加两个数值,但它能让我们专注于打包方面。接下来,我们将添加一个同样愚蠢的:calc.zig

    pub const add = @import("add.zig").add;
    -
    -test {
    -	// By default, only tests in the specified file
    -	// are included. This magic line of code will
    -	// cause a reference to all nested containers
    -	// to be tested.
    -	@import("std").testing.refAllDecls(@This());
    -}
    -
    -

    我们将其分割为 calc.zigadd.zig,以证明 zig build 可以自动构建和打包所有项目文件。最后,我们可以添加 build.zig:

    const std = @import("std");
    -
    -pub fn build(b: *std.Build) !void {
    -	const target = b.standardTargetOptions(.{});
    -	const optimize = b.standardOptimizeOption(.{});
    -
    -	const tests = b.addTest(.{
    -		.target = target,
    -		.optimize = optimize,
    -		.root_source_file = b.path("calc.zig"),
    -	});
    -
    -	const test_cmd = b.addRunArtifact(tests);
    -	test_cmd.step.dependOn(b.getInstallStep());
    -	const test_step = b.step("test", "Run the tests");
    -	test_step.dependOn(&test_cmd.step);
    -}
    -
    -

    这些都是我们在上一节中看到的内容的重复。有了这些,你就可以运行 zig build test --summary all

    回到我们的 learning项目和之前创建的 build.zig。首先,我们将添加本地 calc 作为依赖项。我们需要添加三项内容。首先,我们将创建一个指向 calc.zig的模块:

    // 你可以把这些代码放在构建函数的顶部,
    -// 即调用 addExecutable 之前。
    -
    -const calc_module = b.addModule("calc", .{
    -	.root_source_file = b.path("PATH_TO_CALC_PROJECT/calc.zig"),
    -});
    -
    -

    你需要调整 calc.zig 的路径。现在,我们需要将这个模块添加到现有的 exetests 变量中。由于我们的 build.zig 变得越来越复杂,我们将尝试稍微组织一下:

    const std = @import("std");
    -
    -pub fn build(b: *std.Build) !void {
    -	const target = b.standardTargetOptions(.{});
    -	const optimize = b.standardOptimizeOption(.{});
    -
    -	const calc_module = b.addModule("calc", .{
    -		.root_source_file = b.path("PATH_TO_CALC_PROJECT/calc.zig"),
    -	});
    -
    -	{
    -		// 设置我们的 "run" 命令。
    -
    -		const exe = b.addExecutable(.{
    -			.name = "learning",
    -			.target = target,
    -			.optimize = optimize,
    -			.root_source_file = b.path("learning.zig"),
    -		});
    -		// 添加这些代码
    -		exe.root_module.addImport("calc", calc_module);
    -		b.installArtifact(exe);
    -
    -		const run_cmd = b.addRunArtifact(exe);
    -		run_cmd.step.dependOn(b.getInstallStep());
    -
    -		const run_step = b.step("run", "Start learning!");
    -		run_step.dependOn(&run_cmd.step);
    -	}
    -
    -	{
    -		// 设置我们的 "test" 命令。
    -		const tests = b.addTest(.{
    -			.target = target,
    -			.optimize = optimize,
    -			.root_source_file = b.path("learning.zig"),
    -		});
    -		// 添加这行代码
    -		tests.root_module.addImport("calc", calc_module);
    -
    -		const test_cmd = b.addRunArtifact(tests);
    -		test_cmd.step.dependOn(b.getInstallStep());
    -		const test_step = b.step("test", "Run the tests");
    -		test_step.dependOn(&test_cmd.step);
    -	}
    -}
    -
    -

    现在,可以在项目中 @import("calc")

    const calc = @import("calc");
    -...
    -calc.add(1, 2);
    -
    -

    添加远程依赖关系需要花费更多精力。首先,我们需要回到 calc 项目并定义一个模块。你可能认为项目本身就是一个模块,但一个项目(project)可以暴露多个模块(module),所以我们需要明确地创建它。我们使用相同的 addModule,但舍弃了返回值。只需调用 addModule 就足以定义模块,然后其他项目就可以导入该模块。

    _ = b.addModule("calc", .{
    -	.root_source_file = b.path("calc.zig"),
    -});
    -
    -

    这是我们需要对库进行的唯一改动。因为这是一个远程依赖的练习,所以我把这个 calc 项目推送到了 GitHub,这样我们就可以把它导入到我们的 learning 项目中。它可以在 https://github.com/karlseguin/calc.zig 上找到。

    回到我们的 learning项目,我们需要一个新文件 build.zig.zon。ZON 是 Zig Object Notation 的缩写,它允许以人类可读格式表达 Zig 数据,并将人类可读格式转化为 Zig 代码。build.zig.zon 的内容包括:

    .{
    -  .name = "learning",
    -  .paths = .{""},
    -  .version = "0.0.0",
    -  .dependencies = .{
    -    .calc = .{
    -      .url = "https://github.com/karlseguin/calc.zig/archive/d1881b689817264a5644b4d6928c73df8cf2b193.tar.gz",
    -      .hash = "12ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
    -    },
    -  },
    -}
    -
    -

    该文件中有两个可疑值,第一个是 url 中的 d1881b689817264a5644b4d6928c73df8cf2b193<。这只是 git 提交的哈希值。第二个是哈希值。据我所知,目前还没有很好的方法来告诉我们这个值应该是多少,所以我们暂时使用一个假值。

    要使用这一依赖关系,我们需要对 build.zig 进行一处修改:

    // 将这些代码:
    -const calc_module = b.addModule("calc", .{
    -	.root_source_file = b.path("calc/calc.zig"),
    -});
    -
    -// 替换成:
    -const calc_dep = b.dependency("calc", .{.target = target,.optimize = optimize});
    -const calc_module = calc_dep.module("calc");
    -
    -

    build.zig.zon 中,我们将依赖关系命名为 calc,这就是我们要加载的依赖关系。在这个依赖关系中,我们将使用其中的 calc 模块,也就是我们在 calcbuild.zig.zon 中命名的模块。

    如果你尝试运行 zig build test,应该会看到一个错误:

    hash mismatch: manifest declares
    -122053da05e0c9348d91218ef015c8307749ef39f8e90c208a186e5f444e818672da
    -
    -but the fetched package has
    -122036b1948caa15c2c9054286b3057877f7b152a5102c9262511bf89554dc836ee5
    -
    -

    将正确的哈希值复制并粘贴回 build.zig.zon,然后再次尝试运行 zig build test,现在一切应该都正常了。

    听起来很多,我希望能精简一些。但这主要是你可以从其他项目中复制和粘贴的东西,一旦设置完成,你就可以继续了。

    需要提醒的是,我发现 Zig 对依赖项的缓存偏激。如果你试图更新依赖项,但 Zig 似乎检测不到变化。这时,我会删除项目的 zig-cache 文件夹以及 ~/.cache/zig


    我们已经涉猎了很多领域,探索了一些核心数据结构,并将之前的大块内容整合到了一起。我们的代码变得复杂了一些,不再那么注重特定的语法,看起来更像真正的代码。让我感到兴奋的是,尽管如此复杂,但代码大部分都是有意义的。如果暂时没有看懂,也不要放弃。选取一个示例并将其分解,添加打印语句,为其编写一些测试。亲自动手编写自己的代码,然后再回来阅读那些没有看懂的部分。

    - - - - \ No newline at end of file diff --git a/public/learn/conclusion/index.html b/public/learn/conclusion/index.html deleted file mode 100644 index 6529dfc..0000000 --- a/public/learn/conclusion/index.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - - -
    -

    原文总结:https://www.openmymind.net/learning_zig/conclusion

    有些读者可能会认出我是各种『The Little $TECH Book』 的作者(译者注:原作者还写过 The Little Go BookThe Little MongoDB Book),并想知道为什么这本书不叫『The Little Zig Book』。事实上,我不确定 Zig 是否适合『小』这个范畴。部分挑战在于,Zig 的复杂性和学习曲线会因个人背景和经验的不同而大相径庭。如果你是一个经验丰富的 C 或 C++ 程序员,那么简明扼要地总结一下这门语言可能就够了,这种情况下你可能会更需要Zig 的官方文档

    虽然我们在本指南中涉及了很多内容,但仍有大量内容我们尚未触及。我不希望这让你气馁或不知所措。所有语言的学习都是循序渐进的,通过本教程,你有了一个良好基础,也可以把它当作参考资料,可以开始学习 Zig 语言中更高级的功能。坦率地说,我没有涉及的部分我本身就理解有限,因此无法很好的解释。但这并不妨碍我使用 Zig 编写有意义的东西,比如一个流行的 HTTP 服务器

    最后,我想强调一件完全被略过的事情,你之前可能有所耳闻,即 Zig 与 C 代码交互非常容易。因为 Zig 的生态还很年轻,标准库也很小,所以在某些情况下,使用 C 库可能是最好的选择。例如,Zig 标准库中没有正则表达式模块,使用 C 语言库就是一个合理的选择。我曾为 SQLite 和 DuckDB 编写过 Zig 库,这很简单。如果你基本遵循了本指南中的所有内容,应该不会有任何问题。

    希望本资料对你有所帮助,也希望你能在编程过程中获得乐趣。

    - - - - \ No newline at end of file diff --git a/public/learn/generics/index.html b/public/learn/generics/index.html deleted file mode 100644 index 5e9f0d1..0000000 --- a/public/learn/generics/index.html +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - - -
    -

    原文地址:https://www.openmymind.net/learning_zig/generics

    在上一小节中,我们创建了一个名为 IntList 的动态数组。该数据结构的目标是保存数目不定的数值。虽然我们使用的算法适用于任何类型的数据,但我们的实现与 i64 值绑定。这就需要使用泛型,其目的是从特定类型中抽象出算法和数据结构。

    许多语言使用特殊的语法和特定的泛型规则来实现泛型。而在 Zig 中,泛型并不是一种特定的功能,而更多地体现了语言的能力。具体来说,泛型利用了 Zig 强大的编译时元编程功能。

    我们先来看一个简单的例子,以了解我们的想法:

    const std = @import("std");
    -
    -pub fn main() !void {
    -	var arr: IntArray(3) = undefined;
    -	arr[0] = 1;
    -	arr[1] = 10;
    -	arr[2] = 100;
    -	std.debug.print("{any}\n", .{arr});
    -}
    -
    -fn IntArray(comptime length: usize) type {
    -	return [length]i64;
    -}
    -
    -

    上述代码会打印了 { 1, 10, 100 }。有趣的是,我们有一个返回类型的函数(因此函数是 PascalCase)。这也不是普通的类型,而是由函数参数动态确定的类型。这段代码之所以能运行,是因为我们将 length 声明为 comptime。也就是说,我们要求任何调用 IntArray 的人传递一个编译时已知的长度参数。这是必要的,因为我们的函数返回一个类型,而类型必须始终是编译时已知的。

    函数可以返回任何类型,而不仅仅是基本类型和数组。例如,只需稍作改动,我们就可以让函数返回一个结构体:

    const std = @import("std");
    -
    -pub fn main() !void {
    -	var arr: IntArray(3) = undefined;
    -	arr.items[0] = 1;
    -	arr.items[1] = 10;
    -	arr.items[2] = 100;
    -	std.debug.print("{any}\n", .{arr.items});
    -}
    -
    -fn IntArray(comptime length: usize) type {
    -	return struct {
    -		items: [length]i64,
    -	};
    -}
    -
    -

    也许看起来很奇怪,但 arr 的类型确实是 IntArray(3)。它和其他类型一样,是一个类型,而 arr 和其他值一样,是一个值。如果我们调用 IntArray(7),那就是另一种类型了。也许我们可以让事情变得更简洁:

    const std = @import("std");
    -
    -pub fn main() !void {
    -	var arr = IntArray(3).init();
    -	arr.items[0] = 1;
    -	arr.items[1] = 10;
    -	arr.items[2] = 100;
    -	std.debug.print("{any}\n", .{arr.items});
    -}
    -
    -fn IntArray(comptime length: usize) type {
    -	return struct {
    -		items: [length]i64,
    -
    -		fn init() IntArray(length) {
    -			return .{
    -				.items = undefined,
    -			};
    -		}
    -	};
    -}
    -
    -

    乍一看,这可能并不整齐。但除了匿名和嵌套在一个函数中之外,我们的结构看起来就像我们目前看到的其他结构一样。它有字段,有函数。你知道人们常说『如果它看起来像一只鸭子,那么就就是一只鸭子』。那么,这个结构看起来、游起来和叫起来都像一个正常的结构,因为它本身就是一个结构体。

    希望上面这个示例能让你熟悉返回类型的函数和相应的语法。为了得到一个更典型的范型,我们需要做最后一个改动:我们的函数必须接受一个类型。实际上,这只是一个很小的改动,但 type 会比 usize 更抽象,所以我们慢慢来。让我们进行一次飞跃,修改之前的 IntList,使其能与任何类型一起工作。我们先从基本结构开始:

    fn List(comptime T: type) type {
    -	return struct {
    -		pos: usize,
    -		items: []T,
    -		allocator: Allocator,
    -
    -		fn init(allocator: Allocator) !List(T) {
    -			return .{
    -				.pos = 0,
    -				.allocator = allocator,
    -				.items = try allocator.alloc(T, 4),
    -			};
    -		}
    -	};
    -}
    -
    -

    上面的结构与 IntList 几乎完全相同,只是 i64 被替换成了 T。我们本可以叫它 item_type。不过,按照 Zig 的命名约定,type 类型的变量使用 PascalCase 风格。

    无论好坏,使用单个字母表示类型参数的历史都比 Zig 要悠久得多。在大多数语言中,T 是常用的默认值,但你也会看到根据具体语境而变化的情况,例如哈希映射使用 K 和 V 来表示键和值参数类型。

    如果你对上述代码还有疑问,可以着重看使用 T 的两个地方:items:[]Tallocator.alloc(T, 4)。当我们要使用这个通用类型时,我们将使用

    var list = try List(u32).init(allocator);
    -
    -

    编译代码时,编译器会通过查找每个 T 并将其替换为 u32 来创建一个新类型。如果我们再次使用 List(u32),编译器将重新使用之前创建的类型。如果我们为 T 指定一个新值,例如 List(bool)List(User),就会创建与之对应的新类型。

    为了完成通用的 List,我们可以复制并粘贴 IntList 代码的其余部分,然后用 T 替换 i64

    const std = @import("std");
    -const Allocator = std.mem.Allocator;
    -
    -pub fn main() !void {
    -	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -	const allocator = gpa.allocator();
    -
    -	var list = try List(u32).init(allocator);
    -	defer list.deinit();
    -
    -	for (0..10) |i| {
    -		try list.add(@intCast(i));
    -	}
    -
    -	std.debug.print("{any}\n", .{list.items[0..list.pos]});
    -}
    -
    -fn List(comptime T: type) type {
    -	return struct {
    -		pos: usize,
    -		items: []T,
    -		allocator: Allocator,
    -
    -		fn init(allocator: Allocator) !List(T) {
    -			return .{
    -				.pos = 0,
    -				.allocator = allocator,
    -				.items = try allocator.alloc(T, 4),
    -			};
    -		}
    -
    -		fn deinit(self: List(T)) void {
    -			self.allocator.free(self.items);
    -		}
    -
    -		fn add(self: *List(T), value: T) !void {
    -			const pos = self.pos;
    -			const len = self.items.len;
    -
    -			if (pos == len) {
    -				// we've run out of space
    -				// create a new slice that's twice as large
    -				var larger = try self.allocator.alloc(T, len * 2);
    -
    -				// copy the items we previously added to our new space
    -				@memcpy(larger[0..len], self.items);
    -
    -				self.allocator.free(self.items);
    -
    -				self.items = larger;
    -			}
    -
    -			self.items[pos] = value;
    -			self.pos = pos + 1;
    -		}
    -	};
    -}
    -
    -

    我们的 init 函数返回一个 List(T),我们的 deinitadd 函数使用 List(T)*List(T) 作为参数。在我们的这个简单的示例中,这样做没有问题,但对于大型数据结构,编写完整的通用名称可能会变得有点繁琐,尤其是当我们有多个类型参数时(例如,散列映射的键和值需要使用不同的类型)。@This() 内置函数会返回它被调用时的最内层类型。一般来说,我们会这样定义 List(T)

    fn List(comptime T: type) type {
    -	return struct {
    -		pos: usize,
    -		items: []T,
    -		allocator: Allocator,
    -
    -		// Added
    -		const Self = @This();
    -
    -		fn init(allocator: Allocator) !Self {
    -			// ... same code
    -		}
    -
    -		fn deinit(self: Self) void {
    -			// .. same code
    -		}
    -
    -		fn add(self: *Self, value: T) !void {
    -			// .. same code
    -		}
    -	};
    -}
    -
    -

    Self 并不是一个特殊的名称,它只是一个变量,而且是 PascalCase 风格,因为它的值是一种类型。我们可以在之前使用 List(T) 的地方用 Self 来替代。


    我们可以创建更复杂的示例,使用多种类型参数和更先进的算法。但归根结底,泛型代码的关键点与上述简单示例相差无几。在下一部分,我们将在研究标准库中的 ArrayList(T)StringHashMap(V) 时再次讨论泛型。

    - - - - \ No newline at end of file diff --git a/public/learn/heap-memory-and-allocator/index.html b/public/learn/heap-memory-and-allocator/index.html deleted file mode 100644 index 8d11044..0000000 --- a/public/learn/heap-memory-and-allocator/index.html +++ /dev/null @@ -1,444 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - - -
    - -

    Table of Contents

    -
      -
    • defer 和 errdefer
    • 双重释放和内存泄漏
    • 创建与销毁
    • 分配器 Allocator
    • 通用分配器 GeneralPurposeAllocator
    • std.testing.allocator
    • ArenaAllocator
    • 固定缓冲区分配器 FixedBufferAllocator
    -
    -

    原文地址:https://www.openmymind.net/learning_zig/heap_memory

    迄今为止,我们所接触到的一切都有个限制,需要预先知道大小。数组总是有一个编译时已知的长度(事实上,长度是类型的一部分)。我们所有的字符串都是字符串字面量,其长度在编译时是已知的。

    此外,我们所见过的两种内存管理策略,即全局数据调用栈,虽然简单高效,但都有局限性。这两种策略都无法处理动态大小的数据,而且在数据生命周期方面都很固定。

    本部分分为两个主题。第一个主题是第三个内存区域–堆的总体概述。另一个主题是 Zig 直接而独特的堆内存管理方法。即使你熟悉堆内存,比如使用过 C 语言的 malloc,你也会希望阅读第一部分,因为它是 Zig 特有的。

    堆是我们可以使用的第三个也是最后一个内存区域。与全局数据和调用栈相比,堆有点像蛮荒之地:什么都可以使用。具体来说,在堆中,我们可以在运行时创建大小已知的内存,并完全控制其生命周期。

    调用堆栈之所以令人惊叹,是因为它管理数据的方式简单且可预测(通过压入和弹出堆栈帧)。这一优点同时也是缺点:数据的生命周期与它在调用堆栈中的位置息息相关。堆则恰恰相反。它没有内置的生命周期,因此我们的数据可长可短。这个优点也是它的缺点:它没有内置的生命周期,所以如果我们不释放数据,就没有人会释放。

    让我们来看一个例子:

    const std = @import("std");
    -
    -pub fn main() !void {
    -	// we'll be talking about allocators shortly
    -	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -	const allocator = gpa.allocator();
    -
    -	// ** The next two lines are the important ones **
    -	var arr = try allocator.alloc(usize, try getRandomCount());
    -	defer allocator.free(arr);
    -
    -	for (0..arr.len) |i| {
    -		arr[i] = i;
    -	}
    -	std.debug.print("{any}\n", .{arr});
    -}
    -
    -fn getRandomCount() !u8 {
    -	var seed: u64 = undefined;
    -	try std.posix.getrandom(std.mem.asBytes(&seed));
    -	var random = std.Random.DefaultPrng.init(seed);
    -	return random.random().uintAtMost(u8, 5) + 5;
    -}
    -
    -

    我们稍后将讨论 Zig 的分配器,目前需要知道的是分配器是 std.mem.Allocator 类型。我们使用了它的两种方法:allocfree。分配内存可能出错,故我们用 try 捕获调用 allocator.alloc 产生的错误。目前唯一可能的错误是 OutOfMemory。其参数主要告诉我们它是如何工作的:它需要一个类型(T)和一个计数,成功时返回一个类型为 []T 的切片。它分配发生在运行时期间,必须如此,因为我们的计数只在运行时才可知。

    一般来说,每次 alloc 都会有相应的 freealloc分配内存,free释放内存。不要让这段简单的代码限制了你的想象力。这种 try alloc + defer free 的模式很常见,这是有原因的:在我们分配内存的地方附近释放相对来说是万无一失的。但同样常见的是在一个地方分配,而在另一个地方释放。正如我们之前所说,堆没有内置的生命周期管理。你可以在 HTTP 处理程序中分配内存,然后在后台线程中释放,这是代码中两个完全独立的部分。

    defer 和 errdefer

    说句题外话,上面的代码介绍了一个新的语言特性:defer,它在退出作用域时执行给定的代码。『作用域退出』包括到达作用域的结尾或从作用域返回。严格来说, defer 与分配器或内存管理并无严格关系;你可以用它来执行任何代码。但上述用法很常见。

    Zig 的 defer 类似于 Go 的 defer,但存在一个主要区别。在 Zig 中,defer 将在其包含作用域的末尾运行。在 Go 中,defer 是在包含函数的末尾运行。除非你是 Go 开发人员,否则 Zig 的做法可能更不令人惊讶。

    defer 相似的是 errdefer,它作用与之类似,是在退出作用域时执行给定的代码,但只在返回错误时执行。在进行更复杂的设置时,如果因为出错而不得不撤销之前的分配,这将非常有用。

    以下示例在复杂性上有所增加。它展示了 errdefer 和一个常见的模式,即在 init 函数中分配内存,并在 deinit 中释放:

    const std = @import("std");
    -const Allocator = std.mem.Allocator;
    -
    -pub const Game = struct {
    -	players: []Player,
    -	history: []Move,
    -	allocator: Allocator,
    -
    -	fn init(allocator: Allocator, player_count: usize) !Game {
    -		var players = try allocator.alloc(Player, player_count);
    -		errdefer allocator.free(players);
    -
    -		// store 10 most recent moves per player
    -		var history = try allocator.alloc(Move, player_count * 10);
    -
    -		return .{
    -			.players = players,
    -			.history = history,
    -			.allocator = allocator,
    -		};
    -	}
    -
    -	fn deinit(game: Game) void {
    -		const allocator = game.allocator;
    -		allocator.free(game.players);
    -		allocator.free(game.history);
    -	}
    -};
    -
    -

    这段代码主要突显两件事:

    1. errdefer 的作用。在正常情况下,playerinit 分配,在 deinit 释放。但有一种边缘情况,即 history 初始化失败。在这种情况下,我们需要撤销 players 的分配。
    2. 我们动态分配的两个切片(playershistory)的生命周期是基于我们的应用程序逻辑的。没有任何规则规定何时必须调用 deinit 或由谁调用。这是件好事,因为它为我们提供了任意的生命周期,但也存在缺点,就是如果从未调用 deinit 或调用 deinit 超过一次,就会出现混乱和错误。

    initdeinit 的名字并不特殊。它们只是 Zig 标准库使用的,也是社区采纳的名称。在某些情况下,包括在标准库中,会使用 openclose,或其他更适当的名称。

    双重释放和内存泄漏

    上面提到过,没有规则规定什么时候必须释放什么东西。但事实并非如此,还是有一些重要规则,只是它们不是强制的,需要你自己格外小心。

    第一条规则是不可释放同一内存两次。

    const std = @import("std");
    -
    -pub fn main() !void {
    -	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -	const allocator = gpa.allocator();
    -
    -	var arr = try allocator.alloc(usize, 4);
    -	allocator.free(arr);
    -	allocator.free(arr);
    -
    -	std.debug.print("This won't get printed\n", .{});
    -}
    -
    -

    可以预见到,最后一行代码不会被打印出来。这是因为我们释放了相同的内存两次。这被称为双重释放,是无效的。要避免这种情况似乎很简单,但在具有复杂生命周期的大型项目中,却很难发现。

    第二条规则是,无法释放没有引用的内存。这听起来似乎很明显,但谁负责释放内存并不总是很清楚。下面的代码声明了一个转小写的函数:

    const std = @import("std");
    -const Allocator = std.mem.Allocator;
    -
    -fn allocLower(allocator: Allocator, str: []const u8) ![]const u8 {
    -	var dest = try allocator.alloc(u8, str.len);
    -
    -	for (str, 0..) |c, i| {
    -		dest[i] = switch (c) {
    -			'A'...'Z' => c + 32,
    -			else => c,
    -		};
    -	}
    -
    -	return dest;
    -}
    -
    -

    上面的代码没问题。但以下用法不是:

    // 对于这个特定的代码,我们应该使用 std.ascii.eqlIgnoreCase
    -fn isSpecial(allocator: Allocator, name: [] const u8) !bool {
    -	const lower = try allocLower(allocator, name);
    -	return std.mem.eql(u8, lower, "admin");
    -}
    -
    -

    这是内存泄漏。allocLower 中创建的内存永远不会被释放。不仅如此,一旦 isSpecial 返回,这块内存就永远无法释放。在有垃圾收集器的语言中,当数据变得无法访问时,垃圾收集器最终会释放无用的内存。

    但在上面的代码中,一旦 isSpecial 返回,我们就失去了对已分配内存的唯一引用,即 lower 变量。而直到我们的进程退出后,这块内存块才会释放。我们的函数可能只会泄漏几个字节,但如果它是一个长时间运行的进程,并且重复调用该函数,未被释放的内存块就会逐渐累积起来,最终会耗尽所有内存。

    至少在双重释放的情况下,我们的程序会遭遇严重崩溃。内存泄漏可能很隐蔽。不仅仅是根本原因难以确定。真正的小泄漏或不常执行的代码中的泄漏甚至很难被发现。这是一个很常见的问题,Zig 提供了帮助,我们将在讨论分配器时看到。

    创建与销毁

    std.mem.Allocatoralloc方法会返回一个切片,其长度为传递的第二个参数。如果想要单个值,可以使用 createdestroy 而不是 allocfree

    前面几部分在学习指针时,我们创建了 User 并尝试增强它的功能。下面是基于堆的版本:

    const std = @import("std");
    -
    -pub fn main() !void {
    -	// again, we'll talk about allocators soon!
    -	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -	const allocator = gpa.allocator();
    -
    -	// create a User on the heap
    -	var user = try allocator.create(User);
    -
    -	// free the memory allocated for the user at the end of this scope
    -	defer allocator.destroy(user);
    -
    -	user.id = 1;
    -	user.power = 100;
    -
    -	// this line has been added
    -	levelUp(user);
    -	std.debug.print("User {d} has power of {d}\n", .{user.id, user.power});
    -}
    -
    -fn levelUp(user: *User) void {
    -	user.power += 1;
    -}
    -
    -pub const User = struct {
    -	id: u64,
    -	power: i32,
    -};
    -
    -

    create 方法接受一个参数,类型(T)。它返回指向该类型的指针或一个错误,即 !*T。也许你想知道,如果我们创建了User, 但没有设置 id, power时会发生什么。这就像将这些字段设置为未定义(undefined),其行为也是未定义的。意即,属性没有初始化时,在访问未初始化的变量,行为也是未定义,这意味着程序可能会出现不可预测的行为,比如返回错误的值、崩溃等问题。

    当我们探索悬空指针时,函数错误地返回了本地user的地址:

    pub const User = struct {
    -	fn init(id: u64, power: i32) *User{
    -		var user = User{
    -			.id = id,
    -			.power = power,
    -		};
    -		// this is a dangling pointer
    -		return &user;
    -	}
    -};
    -
    -

    在这种情况下,返回一个 User可能更有意义。但有时你会希望函数返回一个指向它所创建的东西的指针。当你想让生命周期不受调用栈的限制时,你就会这样做。为了解决上面的悬空指针问题,我们可以使用create 方法:

    // 我们的返回类型改变了,因为 init 现在可以失败了
    -// *User -> !*User
    -fn init(allocator: std.mem.Allocator, id: u64, power: i32) !*User{
    -	const user = try allocator.create(User);
    -	user.* = .{
    -		.id = id,
    -		.power = power,
    -	};
    -	return user;
    -}
    -
    -

    我引入了新的语法,user.* = .{...}。这有点奇怪,我不是很喜欢它,但你会看到它。右侧是你已经见过的内容:它是一个带有类型推导的结构体初始化器。我们可以明确地使用 user.* = User{...}。左侧的 user.* 是我们如何去引用该指针所指向的变量。& 接受一个 T 类型并给我们一个 *T 类型。.* 是相反的操作,应用于一个 *T 类型的值时,它给我们一个 T 类型。即,&获取地址,.*获取值。

    请记住,create 返回一个 !*User,所以我们的 user*User 类型。

    分配器 Allocator

    Zig 的核心原则之一是无隐藏内存分配。根据你的背景,这听起来可能并不特别。但这与 C 语言中使用标准库的 malloc 函数分配内存的做法形成了鲜明的对比。在 C 语言中,如果你想知道一个函数是否分配内存,你需要阅读源代码并查找对 malloc 的调用。

    Zig 没有默认的分配器。在上述所有示例中,分配内存的函数都使用了一个 std.mem.Allocator 参数。按照惯例,这通常是第一个参数。所有 Zig 标准库和大多数第三方库都要求调用者在分配内存时提供一个分配器。

    这种显式性有两种形式。在简单的情况下,每次函数调用都会提供分配器。这样的例子很多,但 std.fmt.allocPrint 是你迟早会用到的一个。它类似于我们一直在使用的 std.debug.print,只是分配并返回一个字符串,而不是将其写入 stderr

    const say = std.fmt.allocPrint(allocator, "It's over {d}!!!", .{user.power});
    -defer allocator.free(say);
    -
    -

    另一种形式是将 Allocator 传递给 init ,然后由对象内部使用。这种方法不那么明确,因为你已经给了对象一个分配器来使用,但你不知道哪些方法调用将实际分配。对于长寿命对象来说,这种方法更实用。

    注入分配器的优势不仅在于显式,还在于灵活性。std.mem.Allocator 是一个接口,提供了 allocfreecreatedestroy 函数以及其他一些函数。到目前为止,我们只看到了 std.heap.GeneralPurposeAllocator,但标准库或第三方库中还有其他实现。

    Zig 没有用于创建接口的语法糖。一种类似于接口的模式是带标签的联合(tagged unions),不过与真正的接口相比,这种模式相对受限。整个标准库中也探索了一些其他模式,例如 std.mem.Allocator。本指南不探讨这些接口模式。

    如果你正在构建一个库,那么最好接受一个 std.mem.Allocator,然后让库的用户决定使用哪种分配器实现。否则,你就需要选择正确的分配器,正如我们将看到的,这些分配器并不相互排斥。在你的程序中创建不同的分配器可能有很好的理由。

    通用分配器 GeneralPurposeAllocator

    顾名思义,std.heap.GeneralPurposeAllocator 是一种通用的、线程安全的分配器,可以作为应用程序的主分配器。对于许多程序来说,这是唯一需要的分配器。程序启动时,会创建一个分配器并传递给需要它的函数。我的 HTTP 服务器库中的示例代码就是一个很好的例子:

    const std = @import("std");
    -const httpz = @import("httpz");
    -
    -pub fn main() !void {
    -	// create our general purpose allocator
    -	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -
    -	// get an std.mem.Allocator from it
    -	const allocator = gpa.allocator();
    -
    -	// pass our allocator to functions and libraries that require it
    -	var server = try httpz.Server().init(allocator, .{.port = 5882});
    -
    -	var router = server.router();
    -	router.get("/api/user/:id", getUser);
    -
    -	// blocks the current thread
    -	try server.listen();
    -}
    -
    -

    我们创建了 GeneralPurposeAllocator,从中获取一个 std.mem.Allocator 并将其传递给 HTTP 服务器的 init 函数。在一个更复杂的项目中,声明的变量allocator 可能会被传递给代码的多个部分,每个部分可能都会将其传递给自己的函数、对象和依赖。

    你可能会注意到,创建 gpa 的语法有点奇怪。什么是GeneralPurposeAllocator(.{}){}

    我们之前见过这些东西,只是现在都混合了起来。std.heap.GeneralPurposeAllocator 是一个函数,由于它使用的是 PascalCase(帕斯卡命名法),我们知道它返回一个类型。(下一部分会更多讨论泛型)。也许这个更明确的版本会更容易解读:

    const T = std.heap.GeneralPurposeAllocator(.{});
    -var gpa = T{};
    -
    -// 等同于:
    -
    -var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -
    -

    也许你仍然不太确信 .{} 的含义。我们之前也见过它:.{} 是一个具有隐式类型的结构体初始化器。

    类型是什么,字段在哪里?类型其实是 std.heap.general_purpose_allocator.Config,但它并没有直接暴露出来,这也是我们没有显式给出类型的原因之一。没有设置字段是因为 Config 结构定义了默认值,我们将使用默认值。这是配置、选项的中常见的模式。事实上,我们在下面几行向 init 传递 .{.port = 5882} 时又看到了这种情况。在本例中,除了端口这一个字段外,我们都使用了默认值。

    std.testing.allocator

    希望当我们谈到内存泄漏时,你已经足够烦恼,而当我提到 Zig 可以提供帮助时,你肯定渴望了解更多这方面内容。这种帮助来自 std.testing.allocator,它是一个 std.mem.Allocator 实现。目前,它基于通用分配器(GeneralPurposeAllocator)实现,并与 Zig 的测试运行器进行了集成,但这只是实现细节。重要的是,如果我们在测试中使用 std.testing.allocator,就能捕捉到大部分内存泄漏。

    你可能已经熟悉了动态数组(通常称为 ArrayLists)。在许多动态编程语言中,所有数组都是动态的。动态数组支持可变数量的元素。Zig 有一个通用 ArrayList,但我们将创建一个专门用于保存整数的 ArrayList,并演示泄漏检测:

    pub const IntList = struct {
    -	pos: usize,
    -	items: []i64,
    -	allocator: Allocator,
    -
    -	fn init(allocator: Allocator) !IntList {
    -		return .{
    -			.pos = 0,
    -			.allocator = allocator,
    -			.items = try allocator.alloc(i64, 4),
    -		};
    -	}
    -
    -	fn deinit(self: IntList) void {
    -		self.allocator.free(self.items);
    -	}
    -
    -	fn add(self: *IntList, value: i64) !void {
    -		const pos = self.pos;
    -		const len = self.items.len;
    -
    -		if (pos == len) {
    -			// we've run out of space
    -			// create a new slice that's twice as large
    -			var larger = try self.allocator.alloc(i64, len * 2);
    -
    -			// copy the items we previously added to our new space
    -			@memcpy(larger[0..len], self.items);
    -
    -			self.items = larger;
    -		}
    -
    -		self.items[pos] = value;
    -		self.pos = pos + 1;
    -	}
    -};
    -
    -

    有趣的部分发生在 add 函数里,当 pos == len时,表明我们已经填满了当前数组,并且需要创建一个更大的数组。我们可以像这样使用IntList

    const std = @import("std");
    -const Allocator = std.mem.Allocator;
    -
    -pub fn main() !void {
    -	var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -	const allocator = gpa.allocator();
    -
    -	var list = try IntList.init(allocator);
    -	defer list.deinit();
    -
    -	for (0..10) |i| {
    -		try list.add(@intCast(i));
    -	}
    -
    -	std.debug.print("{any}\n", .{list.items[0..list.pos]});
    -}
    -
    -

    代码运行并打印出正确的结果。不过,尽管我们在 list 上调用了 deinit,还是出现了内存泄漏。如果你没有发现也没关系,因为我们要写一个测试,并使用 std.testing.allocator

    const testing = std.testing;
    -test "IntList: add" {
    -	// We're using testing.allocator here!
    -	var list = try IntList.init(testing.allocator);
    -	defer list.deinit();
    -
    -	for (0..5) |i| {
    -		try list.add(@intCast(i+10));
    -	}
    -
    -	try testing.expectEqual(@as(usize, 5), list.pos);
    -	try testing.expectEqual(@as(i64, 10), list.items[0]);
    -	try testing.expectEqual(@as(i64, 11), list.items[1]);
    -	try testing.expectEqual(@as(i64, 12), list.items[2]);
    -	try testing.expectEqual(@as(i64, 13), list.items[3]);
    -	try testing.expectEqual(@as(i64, 14), list.items[4]);
    -}
    -
    -

    @as 是一个执行类型强制的内置函数。如果你好奇什么我们的测试要用到这么多,那么你不是唯一一个。从技术上讲,这是因为第二个参数,即 actual,被强制为第一个参数,即 expected。在上面的例子中,我们的期望值都是 comptime_int,这就造成了问题。包括我在内的许多人都认为这是一种奇怪而不幸的行为。

    如果你按照步骤操作,把测试放在 IntListmain 的同一个文件中。Zig 的测试通常写在同一个文件中,经常在它们测试的代码附近。当使用 zig test learning.zig 运行测试时,我们会得到了一个令人惊喜的失败:

    Test [1/1] test.IntList: add... [gpa] (err): memory address 0x101154000 leaked:
    -/code/zig/learning.zig:26:32: 0x100f707b7 in init (test)
    -   .items = try allocator.alloc(i64, 2),
    -                               ^
    -/code/zig/learning.zig:55:29: 0x100f711df in test.IntList: add (test)
    - var list = try IntList.init(testing.allocator);
    -
    -... MORE STACK INFO ...
    -
    -[gpa] (err): memory address 0x101184000 leaked:
    -/code/test/learning.zig:40:41: 0x100f70c73 in add (test)
    -   var larger = try self.allocator.alloc(i64, len * 2);
    -                                        ^
    -/code/test/learning.zig:59:15: 0x100f7130f in test.IntList: add (test)
    -  try list.add(@intCast(i+10));
    -
    -

    此处有多个内存泄漏。幸运的是,测试分配器准确地告诉我们泄漏的内存是在哪里分配的。你现在能发现泄漏了吗?如果没有,请记住,通常情况下,每个 alloc 都应该有一个相应的 free。我们的代码在 deinit 中调用 free 一次。然而在 initalloc 被调用一次,每次调用 add 并需要更多空间时也会调用 alloc。每次我们 alloc 更多空间时,都需要 free 之前的 self.items

    // 现有的代码
    -var larger = try self.allocator.alloc(i64, len * 2);
    -@memcpy(larger[0..len], self.items);
    -
    -// 添加的代码
    -// 释放先前分配的内存
    -self.allocator.free(self.items);
    -
    -

    items复制到我们的 larger 切片中后, 添加最后一行free可以解决泄漏的问题。如果运行 zig test learning.zig,便不会再有错误。

    ArenaAllocator

    通用分配器(GeneralPurposeAllocator)是一个合理的默认设置,因为它在所有可能的情况下都能很好地工作。但在程序中,你可能会遇到一些固定场景,使用更专业的分配器可能会更合适。其中一个例子就是需要在处理完成后丢弃的短期状态。解析器(Parser)通常就有这样的需求。一个 parse 函数的基本轮廓可能是这样的

    fn parse(allocator: Allocator, input: []const u8) !Something {
    -	const state = State{
    -		.buf = try allocator.alloc(u8, 512),
    -		.nesting = try allocator.alloc(NestType, 10),
    -	};
    -	defer allocator.free(state.buf);
    -	defer allocator.free(state.nesting);
    -
    -	return parseInternal(allocator, state, input);
    -}
    -
    -

    虽然这并不难管理,但 parseInternal 内可能还会申请临时内存,当然这些内存也需要释放。作为替代方案,我们可以创建一个 ArenaAllocator,一次性释放所有分配:

    fn parse(allocator: Allocator, input: []const u8) !Something {
    -	// create an ArenaAllocator from the supplied allocator
    -	var arena = std.heap.ArenaAllocator.init(allocator);
    -
    -	// this will free anything created from this arena
    -	defer arena.deinit();
    -
    -	// create an std.mem.Allocator from the arena, this will be
    -	// the allocator we'll use internally
    -	const aa = arena.allocator();
    -
    -	const state = State{
    -		// we're using aa here!
    -		.buf = try aa.alloc(u8, 512),
    -
    -		// we're using aa here!
    -		.nesting = try aa.alloc(NestType, 10),
    -	};
    -
    -	// we're passing aa here, so any we're guaranteed that
    -	// any other allocation will be in our arena
    -	return parseInternal(aa, state, input);
    -}
    -
    -

    ArenaAllocator 接收一个子分配器(在本例中是传入 init 的分配器),然后创建一个新的 std.mem.Allocator。当使用这个新的分配器分配或创建内存时,我们不需要调用 free 或 destroy。当我们调用 arena.deinit 时,会一次性释放所有该分配器申请的内存。事实上,ArenaAllocatorfreedestroy 什么也不做。

    必须谨慎使用 ArenaAllocator。由于无法释放单个分配,因此需要确保 ArenaAllocatordeinit 会在合理的内存增长范围内被调用。有趣的是,这种知识可以是内部的,也可以是外部的。例如,在上述代码中,由于状态生命周期的细节属于内部事务,因此在解析器中利用 ArenaAllocator 是合理的。

    ArenaAllocator这样的具有一次性释放所有申请内存的分配器,会破坏每一次 alloc 都应该有相应 free 的规则。不过,如果你收到的是一个 std.mem.Allocator,就不应对其底层实现做任何假设。

    我们的 IntList 却不是这样。它可以用来存储 10 个或 1000 万个值。它的生命周期可以以毫秒为单位,也可以以周为单位。它无法决定使用哪种类型的分配器。使用 IntList 的代码才有这种知识。最初,我们是这样管理 IntList 的:

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -const allocator = gpa.allocator();
    -
    -var list = try IntList.init(allocator);
    -defer list.deinit();
    -
    -

    我们可以选择 ArenaAllocator 替代:

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    -const allocator = gpa.allocator();
    -
    -var arena = std.heap.ArenaAllocator.init(allocator);
    -defer arena.deinit();
    -const aa = arena.allocator();
    -
    -var list = try IntList.init(aa);
    -
    -// 说实话,我很纠结是否应该调用 list.deinit。
    -// 从技术上讲,我们不必这样做,因为我们在上面调用了 defer arena.deinit()。
    -
    -defer list.deinit();
    -
    -...
    -
    -

    由于 IntList 接受的参数是 std.mem.Allocator, 因此我们不需要做什么改变。如果 IntList内部创建了自己的 ArenaAllocator,那也是可行的。允许在ArenaAllocator内部创建ArenaAllocator

    最后举个简单的例子,我上面提到的 HTTP 服务器在响应中暴露了一个 ArenaAllocator。一旦发送了响应,它就会被清空。由于ArenaAllocator的生命周期可以预测(从请求开始到请求结束),因此它是一种高效的选择。就性能和易用性而言,它都是高效的。

    固定缓冲区分配器 FixedBufferAllocator

    我们要讨论的最后一个分配器是 std.heap.FixedBufferAllocator,它可以从我们提供的缓冲区(即 []u8)中分配内存。这种分配器有两大好处。首先,由于所有可能使用的内存都是预先创建的,因此速度很快。其次,它自然而然地限制了可分配内存的数量。这一硬性限制也可以看作是一个缺点。另一个缺点是,freedestroy 只对最后分配/创建的项目有效(想想堆栈)。调用释放非最后分配的内存是安全的,但不会有任何作用。

    译者注:这不是覆盖的问题。FixedBufferAllocator 会按照栈的方式进行内存分配和释放。你可以分配新的内存块,但只能按照后进先出(LIFO)的顺序释放它们。

    const std = @import("std");
    -
    -pub fn main() !void {
    -	var buf: [150]u8 = undefined;
    -	var fa = std.heap.FixedBufferAllocator.init(&buf);
    -
    -	// this will free all memory allocate with this allocator
    -	defer fa.reset();
    -
    -	const allocator = fa.allocator();
    -
    -	const json = try std.json.stringifyAlloc(allocator, .{
    -		.this_is = "an anonymous struct",
    -		.above = true,
    -		.last_param = "are options",
    -	}, .{.whitespace = .indent_2});
    -
    -	// We can free this allocation, but since we know that our allocator is
    -	// a FixedBufferAllocator, we can rely on the above `defer fa.reset()`
    -	defer allocator.free(json);
    -
    -	std.debug.print("{s}\n", .{json});
    -}
    -
    -

    输出内容:

    {
    -  "this_is": "an anonymous struct",
    -  "above": true,
    -  "last_param": "are options"
    -}
    -
    -

    但如果将 buf 更改为 [120]u8,将得到一个内存不足的错误。

    固定缓冲区分配器(FixedBufferAllocators)的常见模式是 reset 并重复使用,竞技场分配器(ArenaAllocators)也是如此。这将释放所有先前的分配,并允许重新使用分配器。


    由于没有默认的分配器,Zig 在分配方面既透明又灵活。std.mem.Allocator接口非常强大,它允许专门的分配器封装更通用的分配器,正如我们在ArenaAllocator中看到的那样。

    更广泛地说,我们希望堆分配的强大功能和相关责任是显而易见的。对于大多数程序来说,分配任意大小、任意生命周期的内存的能力是必不可少的。

    然而,由于动态内存带来的复杂性,你应该注意寻找替代方案。例如,上面我们使用了 std.fmt.allocPrint,但标准库中还有一个 std.fmt.bufPrint。后者使用的是缓冲区而不是分配器:

    const std = @import("std");
    -
    -pub fn main() !void {
    -	const name = "Leto";
    -
    -	var buf: [100]u8 = undefined;
    -	const greeting = try std.fmt.bufPrint(&buf, "Hello {s}", .{name});
    -
    -	std.debug.print("{s}\n", .{greeting});
    -}
    -
    -

    该 API 将内存管理的负担转移给了调用者。如果名称较长或 buf 较小,bufPrint 可能会返回 NoSpaceLeft 的错误。但在很多情况下,应用程序都有已知的限制,例如名称的最大长度。在这种情况下,bufPrint 更安全、更快速。

    动态分配的另一个可行替代方案是将数据流传输到 std.io.Writer。与我们的 Allocator 一样,Writer 也是被许多具体类型实现的接口。上面,我们使用 stringifyAlloc 将 JSON 序列化为动态分配的字符串。我们本可以使用 stringify 将其写入到一个 Writer 中:

    const std = @import("std");
    -
    -pub fn main() !void {
    -	const out = std.io.getStdOut();
    -
    -	try std.json.stringify(.{
    -		.this_is = "an anonymous struct",
    -		.above = true,
    -		.last_param = "are options",
    -	}, .{.whitespace = .indent_2}, out.writer());
    -}
    -
    -

    Allocator通常是函数的第一个参数,而 Writer通常是最后一个参数。ಠ_ಠ

    在很多情况下,用 std.io.BufferedWriter 封装我们的 Writer 会大大提高性能。

    我们的目标并不是消除所有动态分配。这行不通,因为这些替代方案只有在特定情况下才有意义。但现在你有了很多选择。从堆栈到通用分配器,以及所有介于两者之间的东西,比如静态缓冲区、流式 Writer 和专用分配器。

    - - - - \ No newline at end of file diff --git a/public/learn/index.html b/public/learn/index.html deleted file mode 100644 index 8dafc24..0000000 --- a/public/learn/index.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - -

    Learning Zig 中文翻译

    -

    Section Contents

    牟言

    《学习 Zig》系列教程最初由 Karl Seguin 编写,该教程行文流畅,讲述的脉络由浅入深,深入浅出,是入门 Zig 非常不错的选择。因此,Zig 中文社区将其翻译成中文,便于在中文用户内阅读与传播。

    初次接触 Zig 的用户可以按序号依次阅读,对于有经验的 Zig 开发者可按需阅读感兴趣的章节。

    关于原作者

    Karl Seguin 在多个领域有着丰富经验,前微软 MVP,他撰写了大量文章,是多个微软公共新闻组的活跃成员。现居新加坡。他还是以下教程的作者:

    可以在 http://openmymind.net 找到他的博客,或者通过 @karlseguin 在 Twitter 上关注他。

    翻译原则

    技术文档的翻译首要原则是准确,但在准确的前提下如何保证『信、达、雅』?这是个挑战,在翻译本教程时,在某些情况下会根据上下文进行意译,便于中文读者阅读。

    最后,感谢翻译者的无私贡献。❤️️

    离线阅读

    在本仓库的 release 页面会定期将本教程导出为 PDF 格式,读者可按需下载。

    <!-- TODO -->
    -<!-- 读者也可以使用右侧导航栏中的『[整节打印](_print)』将当前版本教程保存为 PDF 格式。 -->
    -
    -

    其他学习资料

    由于 Zig 目前还处于快速迭代,因此最权威的资料无疑是官方的 Zig Language Reference,遇到语言的细节问题,基本都可以在这里找到答案。 其次是社区的一些高质量教程,例如:

    - - - - \ No newline at end of file diff --git a/public/learn/installing-zig/index.html b/public/learn/installing-zig/index.html deleted file mode 100644 index 515e9b0..0000000 --- a/public/learn/installing-zig/index.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - - -
    -

    原文地址:https://www.openmymind.net/learning_zig/#install

    Zig 官网的下载页面中包含常见平台的预编译二进制文件。在这个页面上,你可以找到最新开发版本和主要版本的二进制文件。本指南所跟踪的最新版本可在页面顶部找到。

    对于我的电脑,我会下载 zig-macos-aarch64-0.12.0-dev.161+6a5463951.tar.xz。你使用的可能是不同的平台或更新的版本。展开压缩包,这里面会有一个名为 zig 的二进制文件,你可以按照自己喜欢的方式,为其设置别名(alias)或添加到你的路径(PATH)中。

    现在,你可以运行 zig zenzig version 来测试是否安装正确。

    译者注:建议读者使用版本管理工具来安装 Zig,具体可参考:[《Zig 多版本管理》]({{< ref “/post/2023-10-14-zig-version-manager.org” >}})。

    git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0
    -cat <<'EOF' >> $HOME/.bashrc
    -source "$HOME/.asdf/asdf.sh"
    -source "$HOME/.asdf/completions/asdf.bash"
    -EOF
    -
    -asdf plugin-add zig https://github.com/zigcc/asdf-zig.git
    -
    -# 安装最新版
    -asdf install zig latest
    -asdf global zig latest
    -zig version
    -
    -
    - - - - \ No newline at end of file diff --git a/public/learn/language-overview-part1/index.html b/public/learn/language-overview-part1/index.html deleted file mode 100644 index 727ff48..0000000 --- a/public/learn/language-overview-part1/index.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - - -
    - -

    Table of Contents

    -
      -
    • 模块引用
    • 代码注释
    • 函数
    • 结构体
    • 数组和切片
    • 字符串
    • comptime 和 anytype
    -
    -

    原文地址:https://www.openmymind.net/learning_zig/language_overview_1

    Zig 是一种强类型编译语言。它支持泛型,具有强大的编译时元编程功能,并且不包含垃圾收集器。许多人认为 Zig 是 C 的现代替代品。因此,该语言的语法与 C 类似,比较明显的就是以分号结尾的语句和以花括号分隔的块。

    Zig 代码如下所示:

    const std = @import("std");
    -
    -// 如果 `main` 不是 `pub` (public),此代码将无法编译
    -pub fn main() void {
    -	const user = User{
    -		.power = 9001,
    -		.name = "Goku",
    -	};
    -
    -	std.debug.print("{s}'s power is {d}\n", .{user.name, user.power});
    -}
    -
    -pub const User = struct {
    -	power: u64,
    -	name: []const u8,
    -};
    -
    -

    如果将上述内容保存到 learning.zig 文件,并运行 zig run learning.zig,会得到以下输出:Goku's power is 9001

    这是一个简单的示例,即使你是第一次看到 Zig,大概率能够看懂这段代码。尽管如此,下面的内容我们还是来逐行分析它。

    请参阅安装 Zig 部分,以便快速启动并运行它。

    模块引用

    很少有程序是在没有标准库或外部库的情况下以单个文件编写的。我们的第一个程序也不例外,它使用 Zig 的标准库来进行打印输出。 Zig 的模块系统非常简单,只依赖于 @import 函数和 pub 关键字(使代码可以在当前文件外部访问)。

    @ 开头的函数是内置函数。它们是由编译器提供的,而不是标准库提供的。

    我们通过指定模块名称来引用它。 Zig 的标准库以 std 作为模块名。要引用特定文件,需要使用相对路径。例如,将 User 结构移动到它自己的文件中,比如 models/user.zig

    // models/user.zig
    -pub const User = struct {
    -	power: u64,
    -	name: []const u8,
    -};
    -
    -

    在这种情况下,可以用如下方式引用它:

    // main.zig
    -const User = @import("models/user.zig").User;
    -
    -

    如果我们的 User 结构未标记为 pub 我们会收到以下错误:'User' is not marked 'pub'

    models/user.zig 可以导出不止一项内容。例如,再导出一个常量:

    // models/user.zig
    -pub const MAX_POWER = 100_000;
    -
    -pub const User = struct {
    -	power: u64,
    -	name: []const u8,
    -};
    -
    -

    这时,可以这样导入两者:

    const user = @import("models/user.zig");
    -const User = user.User;
    -const MAX_POWER = user.MAX_POWER;
    -
    -

    此时,你可能会有更多的困惑。在上面的代码片段中,user 是什么?我们还没有看到它,如果使用 var 来代替 const 会有什么不同呢?或者你可能想知道如何使用第三方库。这些都是好问题,但要回答这些问题,需要掌握更多 Zig 的知识点。因此,我们现在只需要掌握以下内容:

    • 如何导入 Zig 标准库
    • 如何导入其他文件
    • 如何导出变量、函数定义

    代码注释

    下面这行 Zig 代码是一个注释:

    // 如果 `main` 不是 `pub` (public),此代码将无法编译
    -
    -

    Zig 没有像 C 语言中类似 /* ... */ 的多行注释。

    基于注释的文档自动生成功能正在试验中。如果你看过 Zig 的标准库文档,你就会看到它的实际应用。//! 被称为顶级文档注释,可以放在文件的顶部。三斜线注释 (///) 被称为文档注释,可以放在特定位置,如声明之前。如果在错误的地方使用这两种文档注释,编译器都会出错。

    函数

    下面这行 Zig 代码是程序的入口函数 main

    pub fn main() void
    -
    -

    每个可执行文件都需要一个名为 main 的函数:它是程序的入口点。如果我们将 main 重命名为其他名字,例如 doIt ,并尝试运行 zig run learning.zig ,我们会得到下面的错误:'learning' has no member named 'main'

    忽略 main 作为程序入口的特殊作用,它只是一个非常基本的函数:不带参数,不返回任何东西(void)。下面的函数会稍微有趣一些:

    const std = @import("std");
    -
    -pub fn main() void {
    -	const sum = add(8999, 2);
    -	std.debug.print("8999 + 2 = {d}\n", .{sum});
    -}
    -
    -fn add(a: i64, b: i64) i64 {
    -	return a + b;
    -}
    -
    -

    C 和 C++ 程序员会注意到 Zig 不需要提前声明,即在定义之前就可以调用 add 函数。

    接下来要注意的是 i64 类型:64 位有符号整数。其他一些数字类型有: u8i8u16i16u32i32u47i47u64i64f32f64

    包含 u47i47 并不是为了测试你是否还清醒; Zig 支持任意位宽度的整数。虽然你可能不会经常使用这些,但它们可以派上用场。经常使用的一种类型是 usize,它是一个无符号指针大小的整数,通常是表示某事物长度、大小的类型。

    除了 f32f64 之外,Zig 还支持 f16f80f128 浮点类型。

    虽然没有充分的理由这样做,但如果我们将 add 的实现更改为:

    fn add(a: i64, b: i64) i64 {
    -	a += b;
    -	return a;
    -}
    -
    -

    a += b 这一行会报下面的错误:不能给常量赋值。这是一个重要的教训,我们稍后将更详细地回顾:函数参数是常量。

    为了提高可读性,Zig 中不支持函数重载(用不同的参数类型或参数个数定义的同名函数)。暂时来说,以上就是我们需要了解的有关函数的全部内容。

    结构体

    下面这行代码创建了一个 User 结构体:

    pub const User = struct {
    -	power: u64,
    -	name: []const u8,
    -};
    -
    -

    由于我们的程序是单个文件,因此 User 仅在定义它的文件中使用,因此我们不需要将其设为 pub 。但这样一来,我们就看不到如何将声明暴露给其他文件了。

    结构字段以逗号终止,并且可以指定默认值:

    pub const User = struct {
    -	power: u64 = 0,
    -	name: []const u8,
    -};
    -
    -

    当我们创建一个结构体时,必须对每个字段赋值。例如,在一开始的定义中 power 没有默认值,因此下面这行代码将报错:missing struct field: power

    const user = User{.name = "Goku"};
    -
    -

    但是,使用默认值定义后,上面的代码可以正常编译。

    结构体可以有方法,也可以包含声明(包括其他结构),甚至可能包含零个字段,此时的作用更像是命名空间。

    pub const User = struct {
    -	power: u64 = 0,
    -	name: []const u8,
    -
    -	pub const SUPER_POWER = 9000;
    -
    -	pub fn diagnose(user: User) void {
    -		if (user.power >= SUPER_POWER) {
    -			std.debug.print("it's over {d}!!!", .{SUPER_POWER});
    -		}
    -	}
    -};
    -
    -

    方法只是普通函数,只是说可以用 struct.method() 方式调用。以下两种方法等价:

    // 调用 user 的 diagnose
    -user.diagnose();
    -
    -// 上面代码等价于:
    -User.diagnose(user);
    -
    -

    大多数时候你将使用struct.method()语法,但方法作为普通函数的语法糖在某些场景下可以派上用场。

    if 语句是我们看到的第一个控制流。这很简单,对吧?我们将在下一部分中更详细地探讨这一点。

    diagnose 在定义 User 类型中,接受 User 作为其第一个参数。因此,我们可以使用struct.method() 的语法来调用它。但结构内的函数不必遵循这种模式。一个常见的例子是用于结构体初始化的 init 函数:

    pub const User = struct {
    -	power: u64 = 0,
    -	name: []const u8,
    -
    -	pub fn init(name: []const u8, power: u64) User {
    -		return User{
    -			.name = name,
    -			.power = power,
    -		};
    -	}
    -}
    -
    -

    init 的命名方式仅仅是一种约定,在某些情况下,open 或其他名称可能更有意义。如果你和我一样,不是 C++ 程序员,可能对 .$field = $value, 这种初始化字段的语法感到奇怪,但你很快就会习惯它。

    当我们创建 "Goku" 时,我们将 user 变量声明为 const

    const user = User{
    -	.power = 9001,
    -	.name = "Goku",
    -};
    -
    -

    这意味着我们无法修改 user 的值。如果要修改变量,应使用 var 声明它。另外,你可能已经注意到 user 的类型是根据赋值对象推导出来的。我们也可以这样明确地声明:

    const user: User = User{
    -	.power = 9001,
    -	.name = "Goku",
    -};
    -
    -

    在有些情况下我们必须显式声明变量类型,但大多数时候,去掉显式的类型会让代码可读性更好。类型推导也可以这么使用。下面这段代码和上面的两个片段是等价的:

    const user: User = .{
    -	.power = 9001,
    -	.name = "Goku",
    -};
    -
    -

    不过这种用法并不常见。比较常见的一种情况是从函数返回结构体时会用到。这里的类型可以从函数的返回类型中推断出来。我们的 init 函数可能会这样写:

    pub fn init(name: []const u8, power: u64) User {
    -	// instead of return User{...}
    -	return .{
    -		.name = name,
    -		.power = power,
    -	};
    -}
    -
    -

    就像我们迄今为止已经探索过的大多数东西一样,今后在讨论 Zig 语言的其他部分时,我们会再次讨论结构体。不过,在大多数情况下,它们都是简单明了的。

    数组和切片

    我们可以略过代码的最后一行,但鉴于我们的代码片段包含两个字符串 "Goku"{s}'s power is {d}\n,你可能会对 Zig 中的字符串感到好奇。为了更好地理解字符串,我们先来了解一下数组和切片。

    数组的大小是固定的,其长度在编译时已知。长度是类型的一部分,因此 4 个有符号整数的数组 [4]i32 与 5 个有符号整数的数组 [5]i32 是不同的类型。

    数组长度可以从初始化中推断出来。在以下代码中,所有三个变量的类型均为 [5]i32

    const a = [5]i32{1, 2, 3, 4, 5};
    -
    -// 我们已经在结构体中使用过 .{...} 语法,
    -// 它也适用于数组
    -
    -const b: [5]i32 = .{1, 2, 3, 4, 5};
    -
    -// 使用 _ 让编译器推导长度
    -const c = [_]i32{1, 2, 3, 4, 5};
    -
    -

    另一方面,切片是指向数组的指针,外加一个在运行时确定的长度。我们将在后面的部分中讨论指针,但你可以将切片视为数组的视图。

    如果你熟悉 Go,你可能已经注意到 Zig 中的切片有点不同:没有容量,只有指针和长度。

    const a = [_]i32{1, 2, 3, 4, 5};
    -const b = a[1..4];
    -
    -

    在上述代码中, b 是一个长度为 3 的切片,并且是一个指向 a 的指针。但是因为我们使用编译时已知的值来对数组进行切片(即 14)所以长度 3 在编译时也是已知。 Zig 编译器能够分析出来这些信息,因此 b 不是一个切片,而是一个指向长度为 3 的整数数组的指针。具体来说,它的类型是 *const [3]i32。所以这个切片的示例被 Zig 编译器的强大推导能力挫败了。

    在实际代码中,切片的使用可能会多于数组。无论好坏,程序的运行时信息往往多于编译时信息。不过,在下面这个例子中,我们必须欺骗 Zig 编译器才能得到我们想要的示例:

    const a = [_]i32{1, 2, 3, 4, 5};
    -var end: usize = 3;
    -end += 1;
    -const b = a[1..end];
    -
    -

    b 现在是一个切片了。具体来说,它的类型是 []const i32。你可以看到,切片的长度并不是类型的一部分,因为长度是运行时属性,而类型总是在编译时就完全已知。在创建切片时,我们可以省略上界,创建一个到要切分的对象(数组或切片)末尾的切片,例如 const c = b[2..]

    如果我们将 end 声明为 const 那么它将成为编译时已知值,这将导致 b 是一个指向数组的指针,而不是切片。我觉得这有点令人困惑,但它并不是经常出现的东西,而且也不太难掌握。我很想在这一点上跳过它,但无法找到一种诚实的方法来避免这个细节。

    学习 Zig 让我了解到,类型具有很强的描述性。它不仅仅是一个整数或布尔值,甚至是一个有符号的 32 位整数数组。类型还包含其他重要信息。我们已经讨论过长度是数组类型的一部分,许多示例也说明了可变性(const-ness)也是数组类型的一部分。例如,在上一个示例中,b 的类型是 []const i32。你可以通过下面的代码来验证这一点:

    const std = @import("std");
    -
    -pub fn main() void {
    -	const a = [_]i32{1, 2, 3, 4, 5};
    -	var end: usize = 3;
    -	end += 1;
    -	const b = a[1..end];
    -	std.debug.print("{any}", .{@TypeOf(b)});
    -}
    -
    -

    如果我们尝试写入 b ,例如 b[2] = 5 ,我们会收到编译时错误:cannot assign to constant.。这就是因为 b 类型是 const 导致。

    为了解决这个问题,你可能会想要进行以下更改:

    // 将 const 替换为 var
    -var b = a[1..end];
    -
    -

    但你会得到同样的错误,为什么?作为提示,b 的类型是什么,或者更通俗地说,b 是什么?切片是指向数组(部分)的长度和指针。切片的类型总是从它所切分的对象派生出来的。无论 b 是否声明为 const,都是一个 [5]const i32 的切片,因此 b 必须是 []const i32 类型。如果我们想写入 b,就需要将 aconst 变为 var

    const std = @import("std");
    -
    -pub fn main() void {
    -	var a = [_]i32{1, 2, 3, 4, 5};
    -	var end: usize = 3;
    -	end += 1;
    -	const b = a[1..end];
    -	b[2] = 99;
    -}
    -
    -

    这是有效的,因为我们的切片不再是 []const i32 而是 []i32 。你可能想知道为什么当 b 仍然是 const 时,这段代码可以执行。这时因为 b 的可变性是指 b 本身,而不是 b 指向的数据。好吧,我不确定这是一个很好的解释,但对我来说,这段代码突出了差异:

    const std = @import("std");
    -
    -pub fn main() void {
    -	var a = [_]i32{1, 2, 3, 4, 5};
    -	var end: usize = 3;
    -	end += 1;
    -	const b = a[1..end];
    -	b = b[1..];
    -}
    -
    -

    上述代码不会编译;正如编译器告诉我们的,我们不能给常量赋值。但如果将代码改成 var b = a[1..end] ,那么代码就是正确的了,因为 b 本身不再是常量。

    在了解 Zig 语言的其他方面(尤其是字符串)的同时,我们还将发现更多有关数组和切片的知识。

    字符串

    我希望我能说,Zig 里有字符串类型,而且非常棒。遗憾的是,它没有。最简单来说,字符串是字节(u8)的序列(即数组或切片)。实际上,我们可以从 name 字段的定义中看到这一点:name: []const u8.

    按照惯例,这类字符串大多数都是用 UTF-8 编码,因为 Zig 源代码本身就是 UTF-8 编码的。但这并不是强制的,而且代表 ASCII 或 UTF-8 字符串的 []const u8 与代表任意二进制数据的 []const u8 实际上并没有什么区别。怎么可能有区别呢,它们是相同的类型。

    根据我们所学的数组和切片知识,你可以正确地猜测 []const u8 是对常量字节数组的切片(其中字节是一个无符号 8 位整数)。但我们的代码中没有任何地方对数组进行切分,甚至没有数组,对吧?我们所做的只是将 "Goku" 赋值给 user.name。这是怎么做到的呢?

    你在源代码中看到的字符串字面量有一个编译时已知的长度。编译器知道 "Goku" 的长度是 4,所以你会认为 "Goku" 最好用数组来表示,比如 [4]const u8。但是字符串字面形式有几个特殊的属性。它们被存储在二进制文件的一个特殊位置,并且会去重。因此,指向字符串字面量的变量将是指向这个特殊位置的指针。也就是说,"Goku" 的类型更接近于 *const [4]u8,是一个指向 4 常量字节数组的指针。

    还有更多。字符串字面量以空值结束。也就是说,它们的末尾总是有一个 \0。在内存中,"Goku" 实际上是这样的:{'G', 'o', 'k', 'u', 0},所以你可能会认为它的类型是 *const [5]u8。但这样做充其量只是模棱两可,更糟糕的是会带来危险(你可能会覆盖空结束符)。相反,Zig 有一种独特的语法来表示以空结尾的数组。"Goku"的类型是 *const[4:0]u8,即 4 字节以空结尾的数组指针。当我们讨论字符串时,我们关注的是以空结尾的字节数组(因为在 C 语言中字符串通常就是这样表示的),语法更通用:[LENGTH:SENTINEL],其中 SENTINEL 是数组末尾的特殊值。因此,虽然我想不出为什么需要它,但下面的语法是完全正确的:

    const std = @import("std");
    -
    -pub fn main() void {
    -	// an array of 3 booleans with false as the sentinel value
    -	const a = [3:false]bool{false, true, false};
    -
    -	// This line is more advanced, and is not going to get explained!
    -	std.debug.print("{any}\n", .{std.mem.asBytes(&a).*});
    -}
    -
    -

    上面代码会输出:{ 0, 1, 0, 0}

    我一直在犹豫是否要加入这个示例,因为最后一行非常高级,我不打算解释它。从另一个角度看,如果你愿意的话,这也是一个可以运行的示例,你可以用它来更好地研究我们到目前为止讨论过的一些问题。

    如果我的解释还可以接受,那么你可能还有一点不清楚。如果 "Goku" 是一个 *const [4:0]u8 ,那么我们为什么能将它赋值给一个 []const u8 值呢?答案很简单:Zig 会自动进行类型转化。它会在几种不同的类型之间进行类型转化,但最明显的是字符串。这意味着,如果函数有一个 []const u8 参数,或者结构体有一个 []const u8 字段,就可以使用字符串字面形式。由于以空结尾的字符串是数组,而且数组的长度是已知的,因此这种转化代价比较低,即不需要遍历字符串来查找空结束符。

    因此,在谈论字符串时,我们通常指的是 []const u8。必要时,我们会明确说明一个以空结尾的字符串,它可以被自动转化为一个 []const u8。但请记住,[]const u8 也用于表示任意二进制数据,因此,Zig 并不像高级编程语言那样有字符串的概念。此外,Zig 的标准库只有一个非常基本的 unicode 模块。

    当然,在实际程序中,大多数字符串(以及更通用的数组)在编译时都是未知的。最典型的例子就是用户输入,程序编译时并不知道用户输入。这一点我们将在讨论内存时再次讨论。但简而言之,对于这种在编译时不能确定值的数据(长度当然也就无从得知),我们将在运行时动态分配内存。我们的字符串变量(仍然是 []const u8 类型)将是指向动态分配的内存的切片。

    comptime 和 anytype

    在我们未解释的最后一行代码中,涉及的知识远比表面看到的多:

    std.debug.print("{s}'s power is {d}\n", .{user.name, user.power});
    -
    -

    我们只是略微浏览了一下,但它确实提供了一个机会来强调 Zig 的一些更强大的功能。即使你还没有掌握,至少也应该了解这些功能。

    首先是 Zig 的编译时执行(compile-time execution)概念。编译时执行是 Zig 元编程功能的核心,顾名思义,就是在编译时而不是运行时运行代码。在本指南中,我们将对编译时可能实现的功能进行浅显介绍,更多高级功能读者可以参考其他资料。

    你可能想知道上面这行代码中需要编译时执行的是什么。print 函数的定义要求我们的第一个参数(字符串格式)是编译时已知的:

    // 注意变量"fmt"前的"comptime"
    -pub fn print(comptime fmt: []const u8, args: anytype) void {
    -
    -

    原因是 print 会进行额外的编译时检查,而这在大多数其他语言中是不会出现的。什么样的检查呢?假设你把格式改为 it's over {d}/n,但保留了两个参数。你会得到一个编译时错误:unused argument in 'it's over {d}'。它还会进行类型检查:将格式字符串改为{s}'s power is {s}\n,你会这个错误invalid format string 's' for type 'u64'。如果在编译时不知道字符串的格式,就不可能在编译时进行这些检查。因此,需要一个编译时已知的值。

    comptime 会对编码产生直接影响的地方是整数和浮点字面的默认类型,即特殊的 comptime_intcomptime_float。这行代码是无效的:var i = 0comptime代码只能使用编译时已知的数据,对于整数和浮点数,这类数据由特殊的 comptime_intcomptime_float 类型标识。这种类型的值可以在编译时执行。但你可能不会把大部分时间花在编写用于编译时执行的代码上,因此它并不是一个特别有用的默认值。你需要做的是给变量一个显式类型:

    var i: usize = 0;
    -var j: f64 = 0;
    -
    -

    注意,如果我们使用const,就不会出现这个错误,因为错误的关键在于 comptime_int 必须是常量。

    在以后的章节中,我们将在探索泛型时进一步研究 comptime

    我们这行代码的另一个特别之处在于奇怪的 .{user.name, user.power},根据上述 print 的定义,我们知道它映射到 anytype 类型的变量。这种类型不应与 Java 的 Object 或 Go 的 any(又名 interface{})混淆。相反,在编译时,Zig 会为传递给它的所有类型专门创建一个单独的 print 函数。

    这就引出了一个问题:我们传递给它的是什么?我们以前在让编译器推断结构类型时见过 .{...} 符号。这与此类似:它创建了一个匿名结构字面。请看这段代码

    pub fn main() void {
    -	std.debug.print("{any}\n", .{@TypeOf(.{.year = 2023, .month = 8})});
    -}
    -
    -

    会输出:

    struct{comptime year: comptime_int = 2023, comptime month: comptime_int = 8}
    -

    在这里,我们给匿名结构的字段取名为 yearmonth。在原始代码中,我们没有这样做。在这种情况下,字段名会自动生成 0、1、2 等。虽然它们都是匿名结构字面形式的示例,但没有字段名称的结构通常被称为“元组”(tuple)。print 函数希望接收一个元组,并使用字符串格式中的序号位置来获取适当的参数。

    Zig 没有函数重载,也没有可变函数(vardiadic,具有任意数量参数的函数)。但它的编译器能根据传入的类型创建专门的函数,包括编译器自己推导和创建的类型。

    - - - - \ No newline at end of file diff --git a/public/learn/language-overview-part2/index.html b/public/learn/language-overview-part2/index.html deleted file mode 100644 index 3497f4c..0000000 --- a/public/learn/language-overview-part2/index.html +++ /dev/null @@ -1,368 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - - -
    - -

    Table of Contents

    -
      -
    • 控制流
    • 枚举
    • 带标签的联合 Tagged Union
    • 可选类型 Optional
    • 未定义的值 Undefined
    • 错误 Errors
    -
    -

    原文地址:https://www.openmymind.net/learning_zig/language_overview_2

    本部分继续上一部分的内容:熟悉 Zig 语言。我们将探索 Zig 的控制流和结构以外的类型。通过这两部分的学习,我们将掌握 Zig 语言的大部分语法,这让我们可以继续深入 Zig 语言,同时也为如何使用 std 标准库打下了基础。

    控制流

    Zig 的控制流很可能是我们所熟悉的,但它与 Zig 语言的其他特性协同工作是我们还没有探索过。我们先简单概述控制流的基本使用,之后在讨论依赖控制流的相关特性时,再来重新回顾。

    你会注意到,我们使用 andor 来代替逻辑运算符 &&||。与大多数语言一样,andor 会短路执行,即如果左侧为假,and 的右侧运算符就不会执行;如果左侧为真,or 的右侧就不会执行。在 Zig 中,控制流是通过关键字完成的,因此要使用 andor

    此外,比较运算符 == 在切片(如 []const u8,即字符串)间不起作用。在大多数情况下,需要使用 std.mem.eql(u8,str1,str2),它将比较两个片段的长度和字节数。

    Zig 中,ifelse ifelse 也很常见:

    // std.mem.eql 将逐字节进行比较,对于字符串来说它是大小写敏感的。
    -if (std.mem.eql(u8, method, "GET") or std.mem.eql(u8, method, "HEAD")) {
    -	// 处理 GET 请求
    -} else if (std.mem.eql(u8, method, "POST")) {
    -	// 处理 POST 请求
    -} else {
    -	// ...
    -}
    -
    -

    std.mem.eql 的第一个参数是一个类型,这里是 u8。这是我们看到的第一个泛型函数。我们将在后面的部分进一步探讨。

    上述示例比较的是 ASCII 字符串,不区分大小写可能更合适,这时 std.ascii.eqlIgnoreCase(str1, str2) 可能是更好的选择。

    虽然没有三元运算符,但可以使用 if/else 来代替:

    const super = if (power > 9000) true else false;
    -
    -

    switch 语句类似于if/else if/else,但具有穷举的优点。也就是说,如果没有涵盖所有情况,编译时就会出错。下面这段代码将无法编译:

    fn anniversaryName(years_married: u16) []const u8 {
    -	switch (years_married) {
    -		1 => return "paper",
    -		2 => return "cotton",
    -		3 => return "leather",
    -		4 => return "flower",
    -		5 => return "wood",
    -		6 => return "sugar",
    -	}
    -}
    -
    -

    编译时会报错:switch 必须处理所有的可能性。由于我们的 years_married 是一个 16 位整数,这是否意味着我们需要处理所有 64K 中情况?是的,不过我们可以使用 else 来代替:

    // ...
    -6 => return "sugar",
    -else => return "no more gifts for you",
    -
    -

    在进行匹配时,我们可以合并多个 case 或使用范围;在进行处理时,可以使用代码块来处理复杂的情况:

    fn arrivalTimeDesc(minutes: u16, is_late: bool) []const u8 {
    -	switch (minutes) {
    -		0 => return "arrived",
    -		1, 2 => return "soon",
    -		3...5 => return "no more than 5 minutes",
    -		else => {
    -			if (!is_late) {
    -				return "sorry, it'll be a while";
    -			}
    -			// todo, something is very wrong
    -			return "never";
    -		},
    -	}
    -}
    -
    -

    虽然 switch 在很多情况下都很有用,但在处理枚举时,它穷举的性质才真正发挥了作用,我们很快就会谈到枚举。

    Zig 的 for 循环用于遍历数组、切片和范围。例如,我们可以这样写:

    fn contains(haystack: []const u32, needle: u32) bool {
    -	for (haystack) |value| {
    -		if (needle == value) {
    -			return true;
    -		}
    -	}
    -	return false;
    -}
    -
    -

    for 循环也可以同时处理多个序列,只要这些序列的长度相同。上面我们使用了 std.mem.eql 函数,下面是其大致实现:

    pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
    -	// if they aren't the same length, they can't be equal
    -	if (a.len != b.len) return false;
    -
    -	for (a, b) |a_elem, b_elem| {
    -		if (a_elem != b_elem) return false;
    -	}
    -
    -	return true;
    -}
    -
    -

    一开始的 if 检查不仅是一个很好的性能优化,还是一个必要的防护措施。如果我们去掉它,并传递不同长度的参数,就会出现运行时 panicfor 在作用于多个序列上时,要求其长度相等。

    for 循环也可以遍历范围,例如:

    for (0..10) |i| {
    -	std.debug.print("{d}\n", .{i});
    -}
    -
    -

    switch 中,范围使用了三个点,即 3...6,而这个示例中,范围使用了两个点,即 0..10。这是因为在 switch 中,范围的两端都是闭区间,而 for 则是左闭右开。

    与一个(或多个)序列组合使用时,它的作用就真正体现出来了:

    fn indexOf(haystack: []const u32, needle: u32) ?usize {
    -	for (haystack, 0..) |value, i| {
    -		if (needle == value) {
    -			return i;
    -		}
    -	}
    -	return null;
    -}
    -
    -

    这是对可空类型的初步了解。

    范围的末端由 haystack 的长度推断,不过我们也可以写出 0..haystack.len,但这没有必要。for 循环不支持常见的 init; compare; step 风格,对于这种情况,可以使用 while

    因为 while 比较简单,形式如下:while (condition) { },这有利于更好地控制迭代。例如,在计算字符串中转义序列的数量时,我们需要将迭代器递增 2 以避免重复计算 \\

    var escape_count: usize = 0;
    -{
    -	var i: usize = 0;
    -	// 反斜杠用作转义字符,因此我们需要用一个反斜杠来转义它。
    -	while (i < src.len) {
    -		if (src[i] == '\\') {
    -			i += 2;
    -			escape_count += 1;
    -		} else {
    -			i += 1;
    -		}
    -	}
    -}
    -
    -

    我们在临时变量 iwhile 循环周围添加了一个显式的代码块。这缩小了 i 的作用范围。这样的代码块可能会很有用,尽管在这个例子中可能有些过度。不过,上述例子已经是 Zig 中最接近传统的 for(init; compare; step) 循环的写法了。

    while 可以包含 else 子句,当条件为假时执行 else 子句。它还可以接受在每次迭代后要执行的语句。多个语句可以用 ; 分隔。在 for 支持遍历多个序列之前,这一功能很常用。上述语句可写成

    var i: usize = 0;
    -var escape_count: usize = 0;
    -
    -// 改写后的
    -while (i < src.len) : (i += 1) {
    -	if (src[i] == '\\') {
    -		// +1 here, and +1 above == +2
    -		// 这里 +1,上面也 +1,相当于 +2
    -		i += 1;
    -		escape_count += 1;
    -	}
    -}
    -
    -
    -

    Zig 也支持 breakcontinue 关键字,用于跳出最内层循环或跳转到下一次迭代。

    代码块可以附带标签(label),breakcontinue 可以作用在特定标签上。举例说明:

    outer: for (1..10) |i| {
    -	for (i..10) |j| {
    -		if (i * j > (i+i + j+j)) continue :outer;
    -		std.debug.print("{d} + {d} >= {d} * {d}\n", .{i+i, j+j, i, j});
    -	}
    -}
    -
    -

    break 还有另一个有趣的行为,即从代码块中返回值:

    const personality_analysis = blk: {
    -	if (tea_vote > coffee_vote) break :blk "sane";
    -	if (tea_vote == coffee_vote) break :blk "whatever";
    -	if (tea_vote < coffee_vote) break :blk "dangerous";
    -};
    -
    -

    像这样有返回值的的块,必须以分号结束。

    稍后,当我们讨论带标签的联合(tagged union)、错误联合(error unions)和可选类型(Optional)时,我们将看到控制流如何与它们联合使用。

    枚举

    枚举是带有标签的整数常量。它们的定义很像结构体:

    // 可以是 "pub" 的
    -const Status = enum {
    -	ok,
    -	bad,
    -	unknown,
    -};
    -
    -

    与结构体一样,枚举可以包含其他定义,包括函数,这些函数可以选择性地将枚举作为第一个参数:

    const Stage = enum {
    -	validate,
    -	awaiting_confirmation,
    -	confirmed,
    -	err,
    -
    -	fn isComplete(self: Stage) bool {
    -		return self == .confirmed or self == .err;
    -	}
    -};
    -
    -

    如果需要枚举的字符串表示,可以使用内置的 @tagName(enum) 函数。

    回想一下,结构类型可以使用 .{...} 符号根据其赋值或返回类型来推断。在上面,我们看到枚举类型是根据与 self 的比较推导出来的,而 self 的类型是 Stage。我们本可以明确地写成:return self == Stage.confirmedself == Stage.err。但是,在处理枚举时,你经常会看到通过 .$value 这种省略具体类型的情况。这被称为枚举字面量

    switch 的穷举性质使它能与枚举很好地搭配,因为它能确保你处理了所有可能的情况。不过在使用 switchelse 子句时要小心,因为它会匹配任何新添加的枚举值,而这可能不是我们想要的行为。

    带标签的联合 Tagged Union

    联合定义了一个值可以具有的一系列类型。例如,这个 Number 可以是整数、浮点数或 nan(非数字):

    const std = @import("std");
    -
    -pub fn main() void {
    -	const n = Number{.int = 32};
    -	std.debug.print("{d}\n", .{n.int});
    -}
    -
    -const Number = union {
    -	int: i64,
    -	float: f64,
    -	nan: void,
    -};
    -
    -

    一个联合一次只能设置一个字段;试图访问一个未设置的字段是错误的。既然我们已经设置了 int 字段,如果我们试图访问 n.float,就会出错。我们的一个字段 nanvoid 类型。我们该如何设置它的值呢?使用 {}

    const n = Number{.nan = {}};
    -
    -

    使用联合的一个难题是要知道设置的是哪个字段。这就是带标签的联合发挥作用的地方。带标签的联合将枚举与联合定义在一起,可用于 switch 语句中。请看下面这个例子:

    pub fn main() void {
    -	const ts = Timestamp{.unix = 1693278411};
    -	std.debug.print("{d}\n", .{ts.seconds()});
    -}
    -
    -const TimestampType = enum {
    -	unix,
    -	datetime,
    -};
    -
    -const Timestamp = union(TimestampType) {
    -	unix: i32,
    -	datetime: DateTime,
    -
    -	const DateTime = struct {
    -		year: u16,
    -		month: u8,
    -		day: u8,
    -		hour: u8,
    -		minute: u8,
    -		second: u8,
    -	};
    -
    -	fn seconds(self: Timestamp) u16 {
    -		switch (self) {
    -			.datetime => |dt| return dt.second,
    -			.unix => |ts| {
    -				const seconds_since_midnight: i32 = @rem(ts, 86400);
    -				return @intCast(@rem(seconds_since_midnight, 60));
    -			},
    -		}
    -	}
    -};
    -
    -

    请注意, switch 中的每个分支捕获了字段的类型值。也就是说,dtTimestamp.DateTime 类型,而 tsi32 类型。这也是我们第一次看到嵌套在其他类型中的结构。DateTime 本可以在联合之外定义。我们还看到了两个新的内置函数:@rem 用于获取余数,@intCast 用于将结果转换为 u16@intCast 从返回值类型中推断出我们需要 u16)。

    从上面的示例中我们可以看出,带标签的联合的使用有点像接口,只要我们提前知道所有可能的实现,我们就能够将其转化带标签的联合这种形式。

    最后,带标签的联合中的枚举类型可以自动推导出来。我们可以直接这样做:

    const Timestamp = union(enum) {
    -	unix: i32,
    -	datetime: DateTime,
    -
    -	...
    -
    -

    这里 Zig 会根据带标签的联合,自动创建一个隐式枚举。

    可选类型 Optional

    在类型前加上问号 ?,任何值都可以声明为可选类型。可选类型既可以是 null,也可以是已定义类型的值:

    var home: ?[]const u8 = null;
    -var name: ?[]const u8 = "Leto";
    -
    -

    明确类型的必要性应该很清楚:如果我们只使用 const name = "Leto",那么推导出的类型将是非可选的 []const u8

    .?用于访问可选类型后面的值:

    std.debug.print("{s}\n", .{name.?});
    -
    -
    -

    但如果在 null 上使用 .?,运行时就会 panicif 语句可以安全地取出可选类型背后的值:

    if (home) |h| {
    -	// h is a []const u8
    -	// we have a home value
    -} else {
    -	// we don't have a home value
    -}
    -
    -

    orelse 可用于提取可选类型的值或执行代码。这通常用于指定默认值或从函数中返回:

    const h = home orelse "unknown"
    -
    -// 或直接返回函数
    -const h = home orelse return;
    -
    -

    不过,orelse 也可以带一个代码块,用于执行更复杂的逻辑。可选类型还可以与 while 整合,经常用于创建迭代器。我们这里忽略迭代器的细节,但希望这段伪代码能说明问题:

    while (rows.next()) |row| {
    -	// do something with our row
    -}
    -
    -

    未定义的值 Undefined

    到目前为止,我们看到的每一个变量都被初始化为一个合理的值。但有时我们在声明变量时并不知道它的值。可选类型是一种选择,但并不总是合理的。在这种情况下,我们可以将变量设置为未定义,让其保持未初始化状态。

    通常这样做的一个地方是创建数组,其值将由某个函数来填充:

    var pseudo_uuid: [16]u8 = undefined;
    -std.crypto.random.bytes(&pseudo_uuid);
    -
    -

    上述代码仍然创建了一个 16 字节的数组,但它的每个元素都没有被赋值。

    错误 Errors

    Zig 中错误处理功能十分简单、实用。这一切都从错误集(error sets)开始,错误集的使用方式类似于枚举:

    // 与第 1 部分中的结构一样,OpenError 也可以标记为 "pub"。
    -// 使其可以在其定义的文件之外访问
    -const OpenError = error {
    -	AccessDenied,
    -	NotFound,
    -};
    -
    -

    任意函数(包括 main)都可以返回这个错误:

    pub fn main() void {
    -	return OpenError.AccessDenied;
    -}
    -
    -const OpenError = error {
    -	AccessDenied,
    -	NotFound,
    -};
    -
    -

    如果你尝试运行这个程序,你会得到一个错误:expected type 'void', found 'error{AccessDenied,NotFound}'。这是有道理的:我们定义了返回类型为 voidmain 函数,但我们却返回了另一种东西(很明显,它是一个错误,而不是 void)。要解决这个问题,我们需要更改函数的返回类型。

    pub fn main() OpenError!void {
    -	return OpenError.AccessDenied;
    -}
    -
    -

    这就是所谓的错误联合类型,它表示我们的函数既可以返回 OpenError 错误,也可以返回 void(也就是什么都没有)。到目前为止,我们已经非常明确:我们为函数可能返回的错误创建了一个错误集,并在函数的错误联合类型中使用了该错误集。但是,说到错误,Zig 有一些巧妙的技巧。首先,我们可以让 Zig 通过使用 !return_type 来推导错误集,而不是将 error union 指定为 error_set!return_type。因此,我们可以(也推荐)将我们 main 函数定义为:

    pub fn main() !void
    -
    -
    -

    其次,Zig 能够为我们隐式创建错误集。我们可以这样做,而不需要提前声明:

    pub fn main() !void {
    -	return error.AccessDenied;
    -}
    -
    -

    完全显式和隐式方法并不完全等同。例如,引用具有隐式错误集的函数时,需要使用特殊的 anyerror 类型。类库开发人员可能会发现显式的好处,比如可以达到代码即文档的效果。不过,我认为隐式错误集和推导错误联合类型都很实用;我在平时编程中,大量使用了这两种方法。

    错误联合类型的真正价值在于 Zig 语言提供了 catchtry 来处理它们。返回错误联合类型的函数调用时,可以包含一个 catch 子句。例如,一个 http 服务器库的代码可能如下所示:

    action(req, res) catch |err| {
    -	if (err == error.BrokenPipe or err == error.ConnectionResetByPeer) {
    -		return;
    -	} else if (err == error.BodyTooBig) {
    -		res.status = 431;
    -		res.body = "Request body is too big";
    -	} else {
    -		res.status = 500;
    -		res.body = "Internal Server Error";
    -		// todo: log err
    -	}
    -};
    -
    -

    switch 的版本更符合惯用法:

    action(req, res) catch |err| switch (err) {
    -	error.BrokenPipe, error.ConnectionResetByPeer) => return,
    -	error.BodyTooBig => {
    -		res.status = 431;
    -		res.body = "Request body is too big";
    -	},
    -	else => {
    -		res.status = 500;
    -		res.body = "Internal Server Error";
    -	}
    -};
    -
    -

    这看起来花哨,但老实说,你在 catch 中最有可能做的事情就是把错误信息给调用者:

    action(req, res) catch |err| return err;
    -
    -

    这种模式非常常见,因此 Zig 提供了 try 关键字用于处理这种情况。上述代码的另一种写法如下:

    try action(req, res);
    -
    -

    鉴于必须处理错误,这一点尤其有用。多数情况下的做法就是使用 trycatch

    Go 开发人员会注意到,tryif err != nil { return err } 的按键次数更少。

    大多数情况下,你都会使用 trycatch,但 ifwhile 也支持错误联合类型,这与可选类型很相似。在 while 的情况下,如果条件返回错误,则执行 else 子句。

    有一种特殊的 anyerror 类型可以容纳任何错误。虽然我们可以将函数定义为返回 anyerror!TYPE 而不是 !TYPE,但两者并不等同。anyerror 是全局错误集,是程序中所有错误集的超集。因此,在函数签名中使用 anyerror 很可能表示这个函数虽然可以返回错误,而实际上它大概率不会返回错误。 anyerror 主要用在可以是任意错误类型的函数参数或结构体字段中(想象一下日志库)。

    函数同时返回可选类型与错误联合类型的情况并不少见。在推导错误集的情况下,形式如下:

    // 载入上次保存的游戏
    -pub fn loadLast() !?Save {
    -	// TODO
    -	return null;
    -}
    -
    -

    使用此类函数有多种方法,但最简洁的方法是使用 try 来解除错误,然后使用 orelse 来解除可选类型。下面是一个大致的模式:

    const std = @import("std");
    -
    -pub fn main() void {
    -	// This is the line you want to focus on
    -	const save = (try Save.loadLast()) orelse Save.blank();
    -	std.debug.print("{any}\n", .{save});
    -}
    -
    -pub const Save = struct {
    -	lives: u8,
    -	level: u16,
    -
    -	pub fn loadLast() !?Save {
    -		//todo
    -		return null;
    -	}
    -
    -	pub fn blank() Save {
    -		return .{
    -			.lives = 3,
    -			.level = 1,
    -		};
    -	}
    -};
    -
    -

    虽然我们还未涉及 Zig 语言中更高级的功能,但我们在前两部分中看到的是 Zig 语言重要组成部分。它们将作为一个基础,让我们能够探索更复杂的话题,而不用被语法所困扰。

    - - - - \ No newline at end of file diff --git a/public/learn/pointers/index.html b/public/learn/pointers/index.html deleted file mode 100644 index 6fcdfd4..0000000 --- a/public/learn/pointers/index.html +++ /dev/null @@ -1,346 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - - -
    - -

    Table of Contents

    -
      -
    • 方法
    • 常量函数参数
    • 指向指针的指针
    • 嵌套指针
    • 递归结构
    -
    -

    原文地址:https://www.openmymind.net/learning_zig/pointers

    Zig 不包含垃圾回收器。管理内存的重任由开发者负责。这是一项重大责任,因为它直接影响到应用程序的性能、稳定性和安全性。

    我们将从指针开始讨论,这本身就是一个重要的话题,同时也是训练我们从面向内存的角度来看待程序数据的开始。如果你已经对指针、堆分配和悬挂指针了如指掌,那么可以跳过本小节和下一小节,直接阅读[堆内存和分配器]({{< ref heap-memory-and-allocator.md >}}),这部分内容与 Zig 更为相关。


    下面的代码创建了一个 power 为 100 的用户,然后调用 levelUp 函数将用户的 power 加一。你能猜到它的输出结果吗?

    const std = @import("std");
    -
    -pub fn main() void {
    -	var user = User{
    -		.id = 1,
    -		.power = 100,
    -	};
    -
    -	// this line has been added
    -	levelUp(user);
    -	std.debug.print("User {d} has power of {d}\n", .{user.id, user.power});
    -}
    -
    -fn levelUp(user: User) void {
    -	user.power += 1;
    -}
    -
    -pub const User = struct {
    -	id: u64,
    -	power: i32,
    -};
    -
    -

    这里我设置了一个陷阱,此段代码将无法编译:局部变量从未被修改。这是指 main 函数中的 user 变量。一个从未被修改的变量必须声明为 const。你可能会想:但在 levelUp 函数中我们确实修改了 user,这怎么回事?让我们假设 Zig 编译器弄错了,并试着糊弄它。我们将强制让编译器看到 user 确实被修改了:

    const std = @import("std");
    -
    -pub fn main() void {
    -	var user = User{
    -		.id = 1,
    -		.power = 100,
    -	};
    -	user.power += 0;
    -
    -	// 代码的其余部分保持不变。
    -
    -

    现在我们在 levelUp 中遇到了一个错误:不能赋值给常量。我们在第一部分中看到函数参数是常量,因此 user.power += 1 是无效的。为了解决这个错误,我们可以将 levelUp 函数改为

    fn levelUp(user: User) void {
    -	var u = user;
    -	u.power += 1;
    -}
    -
    -

    虽然编译成功了,但输出结果却是User 1 has power of 100,而我们代码的目的显然是让 levelUp 将用户的 power 提升到 101。这是怎么回事?

    要理解这一点,我们可以将数据与内存联系起来,而变量只是将类型与特定内存位置关联起来的标签。例如,在 main 中,我们创建了一个User。内存中数据的简单可视化表示如下

    user -> ------------ (id)
    -        |    1     |
    -        ------------ (power)
    -        |   100    |
    -        ------------
    -

    有两点需要注意:

    1. 我们的user变量指向结构的起点
    2. 字段是按顺序排列的

    请记住,我们的user也有一个类型。该类型告诉我们 id 是一个 64 位整数,power 是一个 32 位整数。有了对数据起始位置的引用和类型,编译器就可以将 user.power 转换为:访问位置在结构体第 64 位上的一个 32 位整数。这就是变量的威力,它们可以引用内存,并包含以有意义的方式理解和操作内存所需的类型信息。

    默认情况下,Zig 不保证结构的内存布局。它可以按字母顺序、大小升序或插入填充(padding)某些字段。只要它能正确翻译我们的代码,它就可以为所欲为。这种自由度可以实现某些优化。只有在声明 packed struct时,我们才能获得内存布局的有力保证。我们还可以创建一个 extern struct,这样可以保证内存布局与 C 应用程序二进制接口 (ABI) 匹配。尽管如此,我们对user的可视化还是合理而有用的。

    下面是一个稍有不同的可视化效果,其中包括内存地址。这些数据的起始内存地址是我想出来的一个随机地址。这是user变量引用的内存地址,也是第一个字段 id 的值所在的位置。由于 id 是一个 64 位整数,需要 8 字节内存。因此,power 必须位于 $start_address + 8 上:

    user ->   ------------  (id: 1043368d0)
    -          |    1     |
    -          ------------  (power: 1043368d8)
    -          |   100    |
    -          ------------
    -

    为了验证这一点,我想介绍一下取地址符运算符:&。顾名思义,取地址运算符返回一个变量的地址(它也可以返回一个函数的地址,是不是很神奇?)保留现有的 User 定义,试试下面的代码:

    pub fn main() void {
    -	const user = User{
    -		.id = 1,
    -		.power = 100,
    -	};
    -	std.debug.print("{*}\n{*}\n{*}\n", .{&user, &user.id, &user.power});
    -}
    -
    -

    这段代码输出了useruser.id、和user.power的地址。根据平台等差异,可能会得到不同的输出结果,但都会看到useruser.id的地址相同,而user.power的地址偏移量了 8 个字节。输出的结果如下:

    learning.User@1043368d0
    -u64@1043368d0
    -i32@1043368d8
    -

    取地址运算符返回一个指向值的指针。指向值的指针是一种特殊的类型。类型T的值的地址是*T。因此,如果我们获取 user 的地址,就会得到一个 *User,即一个指向 User 的指针:

    pub fn main() void {
    -	var user = User{
    -		.id = 1,
    -		.power = 100,
    -	};
    -	user.power += 0;
    -
    -	const user_p = &user;
    -	std.debug.print("{any}\n", .{@TypeOf(user_p)});
    -}
    -
    -

    我们最初的目标是通过levelUp函数将用户的power值增加 1 。我们已经让代码编译通过,但当我们打印power时,它仍然是原始值。虽然有点跳跃,但让我们修改代码,在 mainlevelUp 中打印 user的地址:

    pub fn main() void {
    -	var user = User{
    -		.id = 1,
    -		.power = 100,
    -	};
    -	user.power += 0;
    -
    -	// added this
    -	std.debug.print("main: {*}\n", .{&user});
    -
    -	levelUp(user);
    -	std.debug.print("User {d} has power of {d}\n", .{user.id, user.power});
    -}
    -
    -fn levelUp(user: User) void {
    -	// add this
    -	std.debug.print("levelUp: {*}\n", .{&user});
    -	var u = user;
    -	u.power += 1;
    -}
    -
    -

    如果运行这个程序,会得到两个不同的地址。这意味着在 levelUp 中被修改的 usermain 中的user是不同的。这是因为 Zig 传递了一个值的副本。这似乎是一个奇怪的默认行为,但它的好处之一是,函数的调用者可以确保函数不会修改参数(因为它不能)。在很多情况下,有这样的保证是件好事。当然,有时我们希望函数能修改参数,比如 levelUp。为此,我们需要 levelUp 作用于 mainuser,而不是其副本。我们可以通过向函数传递 user的地址来实现这一点:

    const std = @import("std");
    -
    -pub fn main() void {
    -	var user = User{
    -		.id = 1,
    -		.power = 100,
    -	};
    -
    -	// no longer needed
    -	// user.power += 1;
    -
    -	// user -> &user
    -	levelUp(&user);
    -	std.debug.print("User {d} has power of {d}\n", .{user.id, user.power});
    -}
    -
    -// User -> *User
    -fn levelUp(user: *User) void {
    -	user.power += 1;
    -}
    -
    -pub const User = struct {
    -	id: u64,
    -	power: i32,
    -};
    -
    -

    我们必须做两处改动。首先是用 user 的地址(即 &user )来调用 levelUp,而不是 user。这意味着我们的函数参数不再是 User,取而代之的是一个 *User,这是我们的第二处改动。

    我们不再需要通过 user.power += 0; 来强制修改 user 的那个丑陋的技巧了。最初,我们因为 user 是 var 类型而无法让代码编译,编译器告诉我们它从未被修改。我们以为编译器错了,于是通过强制修改来“糊弄”它。但正如我们现在所知道的,在 levelUp 中被修改的 user 是不同的;编译器是正确的。

    现在,代码已按预期运行。虽然在函数参数和内存模型方面仍有许多微妙之处,但我们正在取得进展。现在也许是一个好时机来说明一下,除了特定的语法之外,这些都不是 Zig 所独有的。我们在这里探索的模型是最常见的,有些语言可能只是向开发者隐藏了很多细节,因此也就隐藏了灵活性。

    方法

    一般来说,我们会把 levelUp 写成 User结构的一个方法:

    pub const User = struct {
    -	id: u64,
    -	power: i32,
    -
    -	fn levelUp(user: *User) void {
    -		user.power += 1;
    -	}
    -};
    -
    -

    这就引出了一个问题:我们如何调用带有指针参数的方法?也许我们必须这样做:&user.levelUp()?实际上,只需正常调用即可,即 user.levelUp()。Zig 知道该方法需要一个指针,因此会正确地传递值(通过引用传递)。

    我最初选择函数是因为它很明确,因此更容易学习。

    常量函数参数

    我不止一次暗示过,在默认情况下,Zig 会传递一个值的副本(称为 “按值传递”)。很快我们就会发现,实际情况要更微妙一些(提示:嵌套对象的复杂值怎么办?)

    即使坚持使用简单类型,事实也是 Zig 可以随心所欲地传递参数,只要它能保证代码的意图不受影响。在我们最初的 levelUp 中,参数是一个User,Zig 可以传递用户的副本或对 main.user 的引用,只要它能保证函数不会对其进行更改即可。(我知道我们最终确实希望它被改变,但通过采用 User 类型,我们告诉编译器我们不希望它被改变)。

    这种自由度允许 Zig 根据参数类型使用最优策略。像 User 这样的小类型可以通过值传递(即复制),成本较低。较大的类型通过引用传递可能更便宜。只要代码的意图得以保留,Zig 可以使用任何方法。在某种程度上,使用常量函数参数可以做到这一点。

    现在你知道函数参数是常量的原因之一了吧。

    也许你会想,即使与复制一个非常小的结构相比,通过引用传递怎么会更慢呢?我们接下来会更清楚地看到这一点,但要点是,当 user 是指针时,执行 user.power 会增加一点点开销。编译器必须权衡复制的代价和通过指针间接访问字段的代价。

    指向指针的指针

    我们之前查看了main函数中 user 的内存结构。现在我们改变了 levelUp,那么它的内存会是什么样的呢?

    main:
    -user -> ------------  (id: 1043368d0)  <---
    -        |    1     |                      |
    -        ------------  (power: 1043368d8)  |
    -        |   100    |                      |
    -        ------------                      |
    -                                          |
    -        .............  empty space        |
    -        .............  or other data      |
    -                                          |
    -levelUp:                                  |
    -user -> -------------  (*User)            |
    -        | 1043368d0 |----------------------
    -        -------------
    -

    levelUp 中,user 是指向 User 的指针。它的值是一个地址。当然不是任何地址,而是 main.user 的地址。值得明确的是,levelUp 中的 user 变量代表一个具体的值。这个值恰好是一个地址。而且,它不仅仅是一个地址,还是一个类型,即 *User。这一切都非常一致,不管我们讨论的是不是指针:变量将类型信息与地址联系在一起。指针的唯一特殊之处在于,当我们使用点语法时,例如 user.power,Zig 知道 user 是一个指针,就会自动跟随地址。

    通过指针访问字段时,有些语言可能会使用不同的运算符。

    重要的是要理解,levelUp函数中的user变量本身存在于内存中的某个地址。就像之前所做的一样,我们可以亲自验证这一点:

    fn levelUp(user: *User) void {
    -	std.debug.print("{*}\n{*}\n", .{&user, user});
    -	user.power += 1;
    -}
    -
    -

    上面打印了user变量引用的地址及其值,这个值就是main函数中的user的地址。

    如果user的类型是*User,那么&user呢?它的类型是**User, 或者说是一个指向User指针的指针。我可以一直这样做,直到内存溢出!

    我们可以使用多级间接指针,但这并不是我们现在所需要的。本节的目的是说明指针并不特殊,它只是一个值,即一个地址和一种类型。

    嵌套指针

    到目前为止,User 一直很简单,只包含两个整数。很容易就能想象出它的内存,而且当我们谈论『复制』 时,也不会有任何歧义。但是,如果 User 变得更加复杂并包含一个指针,会发生什么情况呢?

    pub const User = struct {
    -	id: u64,
    -	power: i32,
    -	name: []const u8,
    -};
    -
    -

    我们已经添加了name,它是一个切片。回想一下,切片由长度和指针组成。如果我们使用名字Goku初始化user,它在内存中会是什么样子?

    user -> -------------  (id: 1043368d0)
    -        |     1     |
    -        -------------  (power: 1043368d8)
    -        |    100    |
    -        -------------  (name.len: 1043368dc)
    -        |     4     |
    -        -------------  (name.ptr: 1043368e4)
    -  ------| 1182145c0 |
    -  |     -------------
    -  |
    -  |     .............  empty space
    -  |     .............  or other data
    -  |
    -  --->  -------------  (1182145c0)
    -        |    'G'    |
    -        -------------
    -        |    'o'    |
    -        -------------
    -        |    'k'    |
    -        -------------
    -        |    'u'    |
    -        -------------
    -

    新的name字段是一个切片,由lenptr字段组成。它们与所有其他字段一起按顺序排放。在 64 位平台上,lenptr都将是 64 位,即 8 字节。有趣的是name.ptr的值:它是指向内存中其他位置的地址。

    由于我们使用了字符串字面形式,user.name.ptr 将指向二进制文件中存储所有常量的区域内的一个特定位置。

    通过多层嵌套,类型可以变得比这复杂得多。但无论简单还是复杂,它们的行为都是一样的。具体来说,如果我们回到原来的代码,levelUp 接收一个普通的 User,Zig 提供一个副本,那么现在有了嵌套指针后,情况会怎样呢?

    答案是只会进行浅拷贝。或者像有些人说的那样,只拷贝了变量可立即寻址的内存。这样看来,levelUp 可能只会得到一个 user 残缺副本,name 字段可能是无效的。但请记住,像 user.name.ptr 这样的指针是一个值,而这个值是一个地址。它的副本仍然是相同的地址:

    main: user ->    -------------  (id: 1043368d0)
    -                 |     1     |
    -                 -------------  (power: 1043368d8)
    -                 |    100    |
    -                 -------------  (name.len: 1043368dc)
    -                 |     4     |
    -                 -------------  (name.ptr: 1043368e4)
    -                 | 1182145c0 |-------------------------
    -levelUp: user -> -------------  (id: 1043368ec)       |
    -                 |     1     |                        |
    -                 -------------  (power: 1043368f4)    |
    -                 |    100    |                        |
    -                 -------------  (name.len: 1043368f8) |
    -                 |     4     |                        |
    -                 -------------  (name.ptr: 104336900) |
    -                 | 1182145c0 |-------------------------
    -                 -------------                        |
    -                                                      |
    -                 .............  empty space           |
    -                 .............  or other data         |
    -                                                      |
    -                 -------------  (1182145c0)        <---
    -                 |    'G'    |
    -                 -------------
    -                 |    'o'    |
    -                 -------------
    -                 |    'k'    |
    -                 -------------
    -                 |    'u'    |
    -                 -------------
    -

    从上面可以看出,浅拷贝是可行的。由于指针的值是一个地址,复制该值意味着我们得到的是相同的地址。这对可变性有重要影响。我们的函数不能更改 main.user 中的字段,因为它得到了一个副本,但它可以访问同一个name,那么它能更改 name 吗?在这种特殊情况下,不行,因为 name 是常量。另外,Goku是一个字符串字面量,它总是不可变的。不过,只要花点功夫,我们就能明白浅拷贝的含义:

    const std = @import("std");
    -
    -pub fn main() void {
    -	var name = [4]u8{'G', 'o', 'k', 'u'};
    -	const user = User{
    -		.id = 1,
    -		.power = 100,
    -		// slice it, [4]u8 -> []u8
    -		.name = name[0..],
    -	};
    -	levelUp(user);
    -	std.debug.print("{s}\n", .{user.name});
    -}
    -
    -fn levelUp(user: User) void {
    -	user.name[2] = '!';
    -}
    -
    -pub const User = struct {
    -	id: u64,
    -	power: i32,
    -	// []const u8 -> []u8
    -	name: []u8
    -};
    -
    -

    上面的代码会打印出Go!u。我们不得不将name的类型从[]const u8更改为[]u8,并且不再使用字符串字面量(它们总是不可变的),而是创建一个数组并对其进行切片。有些人可能会认为这前后不一致。通过值传递可以防止函数改变直接字段,但不能改变指针后面有值的字段。如果我们确实希望 name 不可变,就应该将其声明为 []const u8 而不是 []u8

    不同编程语言有不同的实现方式,但许多语言的工作方式与此完全相同(或非常接近)。虽然所有这些看似深奥,但却是日常编程的基础。好消息是,你可以通过简单的示例和片段来掌握这一点;它不会随着系统其他部分复杂性的增加而变得更加复杂。

    递归结构

    有时你需要一个递归结构。在保留现有代码的基础上,我们为 User 添加一个可选的 manager 字段,类型为 ?User。同时,我们将创建两个User,并将其中一个指定为另一个的管理者:

    const std = @import("std");
    -
    -pub fn main() void {
    -	const leto = User{
    -		.id = 1,
    -		.power = 9001,
    -		.manager = null,
    -	};
    -
    -	const duncan = User{
    -		.id = 1,
    -		.power = 9001,
    -		.manager = leto,
    -	};
    -
    -	std.debug.print("{any}\n{any}", .{leto, duncan});
    -}
    -
    -pub const User = struct {
    -	id: u64,
    -	power: i32,
    -	manager: ?User,
    -};
    -
    -

    这段代码无法编译:struct 'learning.User' depends on itself。这个问题的根本原因是每种类型都必须在编译时确定大小,而这里的递归结构体大小是无法确定的。

    我们在添加 name 时没有遇到这个问题,尽管 name可以有不同的长度。问题不在于值的大小,而在于类型本身的大小。name 是一个切片,即 []const u8,它有一个已知的大小:16 字节,其中 len 8 字节,ptr 8 字节。

    你可能会认为这对任何 Optionalunion 来说都是个问题。但对于它们来说,最大字段的大小是已知的,这样 Zig 就可以使用它。递归结构没有这样的上限,该结构可以递归一次、两次或数百万次。这个次数会因User而异,在编译时是不知道的。

    我们通过 name 看到了答案:使用指针。指针总是占用 usize 字节。在 64 位平台上,指针占用 8 个字节。就像Goku并没有与 user一起存储一样,使用指针意味着我们的manager不再与user的内存布局绑定。

    const std = @import("std");
    -
    -pub fn main() void {
    -	const leto = User{
    -		.id = 1,
    -		.power = 9001,
    -		.manager = null,
    -	};
    -
    -	const duncan = User{
    -		.id = 1,
    -		.power = 9001,
    -		// changed from leto -> &leto
    -		.manager = &leto,
    -	};
    -
    -	std.debug.print("{any}\n{any}", .{leto, duncan});
    -}
    -
    -pub const User = struct {
    -	id: u64,
    -	power: i32,
    -	// changed from ?const User -> ?*const User
    -	manager: ?*const User,
    -};
    -
    -

    你可能永远不需要递归结构,但这里并不是介绍数据建模的教程,因此不过多进行介绍。这里主要是想讨论指针和内存模型,以及更好地理解编译器的意图。


    很多开发人员都在为指针而苦恼,因为指针总是难以捉摸。它们给人的感觉不像整数、字符串或User那样具体。虽然你现在不必完全理解这些概念,但掌握它们是值得的,而且不仅仅是为了 Zig。这些细节可能隐藏在 Ruby、Python 和 JavaScript 等语言中,其次是 C#、Java 和 Go。它影响着你如何编写代码以及代码如何运行。因此,请慢慢来,多看示例,添加调试打印语句来查看变量及其地址。你探索得越多,就会越清楚。

    - - - - \ No newline at end of file diff --git a/public/learn/preface/index.html b/public/learn/preface/index.html deleted file mode 100644 index a20606a..0000000 --- a/public/learn/preface/index.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - - -
    -

    欢迎阅读 Zig 编程语言入门指南《学习 Zig》。本指南旨在让你轻松掌握 Zig。本指南假定你已有编程经验,语言不限。

    Zig 目前正在紧锣密鼓地开发中,Zig 语言及其标准库都在不断发展。本指南以最新的 Zig 开发版本为目标。不过,部分代码有可能编译不通过。如果你下载了最新版本的 Zig,但在运行某些代码时遇到问题,请提 issue

    - - - - \ No newline at end of file diff --git a/public/learn/stack-memory/index.html b/public/learn/stack-memory/index.html deleted file mode 100644 index 06fc12f..0000000 --- a/public/learn/stack-memory/index.html +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - - -
    - -

    Table of Contents

    -
      -
    • 栈帧
    • 悬空指针
    -
    -

    原文地址:https://www.openmymind.net/learning_zig/stack_memory

    通过深入研究指针,我们了解了变量、数据和内存之间的关系。因此,我们对内存的分布有了一定的了解,但我们还没有讨论如何管理数据以及内存。对于运行时间短和简单的脚本来说,这可能并不重要。在 32GB 笔记本电脑时代,你可以启动程序,使用几百兆内存读取文件和解析 HTTP 响应,做一些了不起的事情,然后退出。程序退出时,操作系统会知道,它给程序分配的内存可以被回收,并用于其他用途了。

    但对于运行数天、数月甚至数年的程序来说,内存就成了有限而宝贵的资源,很可能会被同一台机器上运行的其他进程抢占。根本不可能等到程序退出后再释放内存。这就是垃圾回收器的主要工作:了解哪些数据不再使用,并释放其内存。在 Zig 中,你就是垃圾回收器。

    我们编写的大多数程序都会使用内存的三个区域。第一个是全局空间,也就是存储程序常量(包括字符串字面量)的地方。所有全局数据都被嵌入到二进制文件中,在编译时(也就是运行时)完全已知,并且不可更改。这些数据在程序的整个生命周期中都存在,从不需要增加或减少内存。除了会影响二进制文件的大小外,我们完全不必担心这个问题。

    内存的第二个区域是调用栈,也是本小节的主题。第三个区域是堆,将在下一小节讨论。

    三块内存区域实际上没有真正的物理差别。操作系统和可执行文件创造了“内存区域”这个概念。

    栈帧

    迄今为止,我们所见的所有数据都是常量,存储在二进制的全局数据部分或作为局部变量。局部表示该变量只在其声明的范围内有效。在 Zig 中,范围从花括号开始到结束。大多数变量的范围限定在一个函数内,包括函数参数,或一个控制流块,比如 if。但是,正如所见,你可以创建任意块,从而创建任意范围。

    在上一部分中,我们可视化了 mainlevelUp 函数的内存,每个函数都有一个 User:

    main: user ->    -------------  (id: 1043368d0)
    -                 |     1     |
    -                 -------------  (power: 1043368d8)
    -                 |    100    |
    -                 -------------  (name.len: 1043368dc)
    -                 |     4     |
    -                 -------------  (name.ptr: 1043368e4)
    -                 | 1182145c0 |-------------------------
    -levelUp: user -> -------------  (id: 1043368ec)       |
    -                 |     1     |                        |
    -                 -------------  (power: 1043368f4)    |
    -                 |    100    |                        |
    -                 -------------  (name.len: 1043368f8) |
    -                 |     4     |                        |
    -                 -------------  (name.ptr: 104336900) |
    -                 | 1182145c0 |-------------------------
    -                 -------------                        |
    -                                                      |
    -                 .............  empty space           |
    -                 .............  or other data         |
    -                                                      |
    -                 -------------  (1182145c0)        <---
    -                 |    'G'    |
    -                 -------------
    -                 |    'o'    |
    -                 -------------
    -                 |    'k'    |
    -                 -------------
    -                 |    'u'    |
    -                 -------------
    -

    levelUp 紧接在 main 之后是有原因的:这是我们的简化版调用栈。当我们的程序启动时,main 及其局部变量被推入调用栈。当 levelUp 被调用时,它的参数和任何局部变量都会被添加到调用栈上。重要的是,当 levelUp 返回时,它会从栈中弹出。 在 levelUp 返回并且控制权回到 main 后,我们的调用栈如下所示:

    main: user ->    -------------  (id: 1043368d0)
    -                 |     1     |
    -                 -------------  (power: 1043368d8)
    -                 |    100    |
    -                 -------------  (name.len: 1043368dc)
    -                 |     4     |
    -                 -------------  (name.ptr: 1043368e4)
    -                 | 1182145c0 |-------------------------
    -                 -------------                        |
    -                                                      |
    -                 .............  empty space           |
    -                 .............  or other data         |
    -                                                      |
    -                 -------------  (1182145c0)        <---
    -                 |    'G'    |
    -                 -------------
    -                 |    'o'    |
    -                 -------------
    -                 |    'k'    |
    -                 -------------
    -                 |    'u'    |
    -                 -------------
    -

    当一个函数被调用时,其整个栈帧被推入调用栈——这就是我们需要知道每种类型大小的原因之一。尽管我们可能直到特定的代码行执行时,才能知道我们 user 的名字的长度(假设它不是一个常量字符串字面量),但我们知道我们的函数有一个 User 类型的变量,除了其他字段,只需要 8 字节来存储name.len和 8 字节来存储name.ptr

    当函数返回时,它的栈帧(最后推入调用栈的帧)会被弹出。令人惊讶的事情刚刚发生:levelUp 使用的内存已被自动释放!虽然从技术上讲,这些内存可以返回给操作系统,但据我所知,没有任何实现会真正缩小调用栈(不过,在必要时,实现会动态增加调用栈)。不过,用于存储 levelUp 堆栈帧的内存现在可以在我们的进程中用于另一个堆栈帧了。

    在普通程序中,调用堆栈可能会变得很大。在一个典型程序所使用的所有框架代码和库之间,你最终会发现深层嵌套的函数。通常情况下,这并不是问题,但有时你可能会遇到堆栈溢出错误。当我们的调用栈空间耗尽时,就会发生这种情况。这种情况通常发生在递归函数中,即函数会调用自身。

    与全局数据一样,调用栈也由操作系统和可执行文件管理。程序启动时,以及此后启动的每个线程,都会创建一个调用栈(其大小通常可在操作系统中配置)。调用栈在程序的整个生命周期中都存在,如果是线程,则在线程的整个生命周期中都存在。程序或线程退出时,调用栈将被释放。我们的全局数据包含所有程序的全局数据,而调用栈只包含当前执行的函数层次的栈帧。这样做既能有效利用内存,又能简化堆栈帧的管理。

    悬空指针

    栈帧的简洁和高效令人惊叹。但它也很危险:当函数返回时,它的任何本地数据都将无法访问。这听起来似乎很合理,毕竟这是本地数据,但却会带来严重的问题。请看这段代码:

    const std = @import("std");
    -
    -pub fn main() void {
    -	const user1 = User.init(1, 10);
    -	const user2 = User.init(2, 20);
    -
    -	std.debug.print("User {d} has power of {d}\n", .{user1.id, user1.power});
    -	std.debug.print("User {d} has power of {d}\n", .{user2.id, user2.power});
    -}
    -
    -pub const User = struct {
    -	id: u64,
    -	power: i32,
    -
    -	fn init(id: u64, power: i32) *User{
    -		var user = User{
    -			.id = id,
    -			.power = power,
    -		};
    -		return &user;
    -	}
    -};
    -
    -

    粗瞥一眼,预期会有下面的输出:

    User 1 has power of 10
    -User 2 has power of 20
    -
    -

    但实际上:

    User 2 has power of 20
    -User 9114745905793990681 has power of 0
    -
    -

    你可能会得到不同的结果,但根据我的输出,user1继承了user2的值,而user2的值是无意义的。这段代码的关键问题是User.init返回局部user的地址&user。这被称为悬空指针,是指引用无效内存的指针。它是许多段错误(segfaults)的源头。

    当一个栈帧从调用栈中弹出时,我们对该内存的任何引用都是无效的。尝试访问该内存的结果是未定义的。你可能会得到无意义的数据或段错误。我们可以试图理解我的输出,但这不是我们想要或甚至可以依赖的行为。

    这类错误的一个挑战是,在有垃圾回收器的语言中,上述代码完全没有问题。例如,Go 会检测局部变量 user 超出了 init 函数的作用域,并在需要时确保其有效性(Go 如何做到这一点是一个实现细节,但它有几个选项,包括将数据移动到堆中,这就是下一部分要讨论的内容)。

    而另一个问题,很遗憾地说,它是一个难以发现的错误。在我们上面的例子中,我们显然返回了一个局部地址。但这种行为可以隐藏在嵌套函数和复杂数据类型中。你是否看到了以下不完整代码的任何可能问题:

    fn read() !void {
    -	const input = try readUserInput();
    -	return Parser.parse(input);
    -}
    -
    -

    无论Parser.parse返回什么,它都将比变量input存在更久。如果Parser持有对 input 的引用,那将是一个悬空指针,等待着让我们的应用程序崩溃。理想情况下,如果 Parser 需要 input 生命周期尽可能长,它将复制 input,并且该复制将与它自己的生命周期绑定(更多内容在下一部分)。但此处没有执行这一步骤。Parser 的文档可能会对它对 input 的期望或它如何使用 input 提供一些说明。缺少这些信息,我们可能需要深入代码来弄清楚。

    为了解决我们上面例子里的错误,有个简单的方法是改变 init,使它返回一个 User 而不是*User(指向 User 的指针)。我们可以使用 return user 而非 return &user。但这并不总是可能的。数据经常需要超越函数作用域的严格界限。为此,我们有了第三个内存区域–堆,这也是下一部分的主题。

    在深入研究堆之前,我们要知道,在本指南结束之前,我们还将看到最后一个关于悬挂指针的示例。到那时,我们已经掌握了足够多的语言知识,可以给出一个不太复杂的示例。我之所以想重提这个话题,是因为对于来自垃圾回收语言的开发人员来说,这很可能会导致错误和挫败感。这一点你会掌握的。归根结底,就是要意识到数据的生命周期。

    - - - - \ No newline at end of file diff --git a/public/learn/style-guide/index.html b/public/learn/style-guide/index.html deleted file mode 100644 index 260a84c..0000000 --- a/public/learn/style-guide/index.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - - -
    - -

    Table of Contents

    -
      -
    • 未使用变量 Unused Variable
    • 变量覆盖 Variable Shadowing
    • 命名规范
    -
    -

    原文地址:https://www.openmymind.net/learning_zig/style_guide

    本小节的主要内容是介绍 Zig 编译器强制遵守的 2 条规则,以及 Zig 标准库的命名惯例(naming convention)。

    未使用变量 Unused Variable

    Zig 编译器禁止未使用变量,例如以下代码会导致两处编译错误:

    const std = @import("std");
    -
    -pub fn main() void {
    -	const sum = add(8999, 2);
    -}
    -
    -fn add(a: i64, b: i64) i64 {
    -	// notice this is a + a, not a + b
    -	return a + a;
    -}
    -
    -

    第一个编译错误,源自于sum是一个未使用的本地常量。第二个编译错误,在于在函数add的所有形参中,b是一个未使用的函数参数。对于这段代码来说,它们是比较明显的漏洞。但是在实际编程中,代码中包含未使用变量和函数形参并非完全不合理。在这种情况下,我们可以通过将未使用变量赋值给_(下划线)的方法,避免编译器报错:

    const std = @import("std");
    -
    -pub fn main() void {
    -	_ = add(8999, 2);
    -
    -	// or
    -
    -	const sum = add(8999, 2);
    -	_ = sum;
    -}
    -
    -fn add(a: i64, b: i64) i64 {
    -	_ = b;
    -	return a + a;
    -}
    -
    -

    除了使用_ = b之外,我们还可以直接用_来命名函数add的形参。但是,在我看来,这样做会牺牲代码的可读性,读者会猜测,这个未使用的形参到底是什么:

    fn add(a: i64, _: i64) i64 {
    -
    -

    值得注意的是,在上述例子中,std也是一个未使用的符号,但是当前这种用法并不会导致任何编译错误。可能在未来,Zig 编译器也将此视为错误。

    变量覆盖 Variable Shadowing

    Zig 不允许使用同名的变量。下面是一个读取 socket 的例子,这个例子包含了一个变量覆盖的编译错误:

    fn read(stream: std.net.Stream) ![]const u8 {
    -	var buf: [512]u8 = undefined;
    -	const read = try stream.read(&buf);
    -	if (read == 0) {
    -		return error.Closed;
    -	}
    -	return buf[0..read];
    -}
    -
    -

    上述例子中,read变量覆盖了read函数。我并不太认同这个规范,因为它会导致开发者为了避免覆盖而使用短且无意义的变量名。例如,为了让上述代码通过编译,需要将变量名read改成n

    我认为,这个规范并不能使代码可读性提高。在这个场景下,应该是开发者,而不是编译器,更有资格选择更有可读性的命名方案。

    命名规范

    除了遵守以上这些规则以外,开发者可以自由地选择他们喜欢的命名规范。但是,理解 Zig 自身的命名规范是有益的,因为大部分你需要打交道的代码,如 Zig 标准库,或者其他三方库,都采用了 Zig 的命名规范。

    Zig 代码采用 4 个空格进行缩进。我个人会因为客观上更方便,使用tab键。

    Zig 的函数名采用了驼峰命名法(camelCase),而变量名会采用小写加下划线(snake case)的命名方式。类型则采用的是 PascalCase 风格。除了这三条规则外,一个有趣的交叉规则是,如果一个变量表示一个类型,或者一个函数返回一个类型,那么这个变量或者函数遵循 PascalCase。在之前的章节中,其实已经见到了这个例子,不过,可能你没有注意到:

    std.debug.print("{any}\n", .{@TypeOf(.{.year = 2023, .month = 8})});
    -
    -

    我们已经看到过一些内置函数:@import@rem@intCast。因为这些都是函数,他们的命名遵循驼峰命名法。@TypeOf也是一个内置函数,但是他遵循 PascalCase,为何?因为他返回的是一个类型,因此它的命名采用了类型命名方法。当我们使用一个变量,去接收@TypeOf的返回值,这个变量也需要遵循类型命名规则(即 PascalCase):

    const T = @TypeOf(3);
    -std.debug.print("{any}\n", .{T});
    -
    -

    zig 命令包含一个 fmt 子命令,在给定一个文件或目录时,它会根据 Zig 的编码风格对文件进行格式化。但它并没有包含所有上述的规则,比如它能够调整缩排,以及花括号{的位置,但是它不会调整标识符的大小写。

    - - - - \ No newline at end of file diff --git a/public/monthly/index.html b/public/monthly/index.html deleted file mode 100644 index e69de29..0000000 diff --git a/public/post/first-post/fanzine.jpg b/public/post/first-post/fanzine.jpg deleted file mode 100644 index 9d6e569f9c2dc06fa61d7b2ebb8e3c8fe4c871fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124852 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�D!G8oJ(CKWoIR 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%0DwhC-?>Fc_(wk|?kH8I^_L0u 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 diff --git a/public/post/first-post/index.html b/public/post/first-post/index.html deleted file mode 100644 index 29650e0..0000000 --- a/public/post/first-post/index.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - -

    First Post: What's A Zine?

    -

    This is a sample first post for your blog.

    This post is defined in content/blog/first-post/ and contains the following files:

    • index.smd
    • fanzine.jpg

    Another interesting thing about this post is that it uses the layouts/post.shtml template which adds at the bottom page navigation within the blog section.

    Enough about Zine, let’s talk about zines.

    Fanzines

    A zine (short for fanzine, from “fan” + “magazine”) is a non-professional publication created by people that want to express themselves in paper form, usually in relation to a cultural phenomenon of some kind.

    A photo of a black and white flyer. The main title reads 'A night of pure energy' and the rest of the page contains a mix of pictures of guitarists interleaved with other text snippets, some seemingly taken from a professional publication of some kind, and some handwritten to announce concert night dates, presumably for the musicians in the picture. -
    An example of zine from 1976.

    Zines in the digital era

    In the digital age, some of the cultural impetus behind zines got redirected to personal blogs and similar digital, non-professional publications. The 90’s and early 00’s are famous for their whacky websites full of clip art, wordart text and “under construction” animated gifs.

    This initial organic exploration didn’t last for too long as the rise of social media diverted a lot of self-expression energy towards walled gardens, a change that was also fueled by the much tighter and intense feedback loop that those platform enable.

    In the 90’s the stongest dopamine hit you could get was adding a visit counter to your altavista website and watch it go up, slowly, mostly because of your own accesses. In modern times views are almost meaningless and interactions such as likes, retweets and comments provide much stronger positive feedback.

    An unfortunate side effect of this new cultural wave centered around social media is that not only you end up gifting your content to platform owners, but you also participate in a system where the language of the social media site shapes your thoughts and experience in specific, and often user-hostile, ways.

    Art, just like liquids, takes the shape of the container you put it in. A mobile game that lives off of in app purchases will never be truly great because of the tension between making the game entertaining enough to keep players engaged, and the need to make it boring enough so that they will want to buy upgrades to make the game more fun.

    Similarly, self-expression on Twitter is encouraged to take the shape of short, hyperbolic hot takes that forgo nuance in order to create catchy quips that can be used for hasty decision making.

    Likewise, corpowave never goes out of style on LinkedIn. Spend enough time in there and you will become a character from Severance.

    We’re not in 1990 anymore

    Despite all the issues with social media, there is no point in thinking of the 90’s as a better time. It was not. And despite the winks at the past, Zine is not a tool for indulging in nostalgia.

    The goal is to make art: the act of inducing a change in others through our self-expression.

    You could argue that the 90’s excelled at self-expression, but in doing so you would also have to accept that social media is infinitely more effective at inducing change in others (albeit at the expense of freedom of expression).

    Once you realize that, the path forward is clear:

    1. Own your content.
    2. Create new social systems that optimize for creating art over engagement.

    Owning your content means that you will be unaffected by enshittification of platforms that would otherwise keep your data hostage. It also is the single most effective thing you can do as an individual to take power away from platforms, all while protecting your own immediate interests.

    Creation of new social systems is a slightly more hairy problem than self hosting a static website, but it’s something that can be done. Over the years we’ve had plenty of social outlets that have allowed people to socialize through their homemade games, music, drawings, fanfics, etc; and chances are that we have yet many more of these outlets ahead of us to create.

    Zine gives you a small puzzle piece to help you inch closer toward a better future, partially by providing you with a new iteration over tried and true patterns (e.g. by facilitating content creation by separating content from layouting concerns as much as possible), and also by being a bit experimental with the concept of a devlog, something that you wouldn’t normally expect to find on a static website.

    Lastly, Zine makes sure your content (both blog and devlog, but also any other content format you might come up with yourself) is available via RSS syndication. RSS feeds are far from a winning technology in the fight against the ebb and enshittyflow of social media, but they are another small puzzle piece that costs nothing to maintain and that might turn out to be critical once enough other preconditions are met.

    With that in mind, go make art with your words.

    – Loris

    - - - - - \ No newline at end of file diff --git a/public/post/index.html b/public/post/index.html deleted file mode 100644 index 643737e..0000000 --- a/public/post/index.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - -

    Blog

    -

    This page defines the blog section and lists all posts in it.

    A “site section” in Zine is a group of pages that form a logical subtree of the website. It’s related to directory structure, but it’s not an entirely 1:1 mapping.

    What defines a site section in Zine is the presence of index.smd files. You can learn more in the official Zine docs.

    Take also a look at layouts/blog.shtml to get an idea of how to render a page list in a SuperHTML template.

    The blog section also has an RSS feed.

    In Zine, RSS feeds are considered “alternative” versions of an existing page. In concrete defines the blog section and that lists all pages in it, is rendered in two versions: HTML for human readers, and XML for RSS readers.

    This is the SuperMD frontmatter code that defines the RSS feed:

    .alternatives = [{ 
    -    .name = "rss",
    -    .layout = "rss.xml", 
    -    .output = "index.xml",
    -}],
    -
    -

    (btw syntax highlighting is done statically in Zine, no need for javascript libraries, unless you want to)

    - - - - - \ No newline at end of file diff --git a/public/post/index.xml b/public/post/index.xml deleted file mode 100644 index 11b0433..0000000 --- a/public/post/index.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - Zig 语言中文社区 - https://example.com - Zig 语言中文社区 - Blog - Zine -- https://zine-ssg.io - en-US - Sat, 28 Jun 2025 03:40:44 +0000 - - - Second Post - <p>This second post is mainly here to show you that you can also create single file posts for convenience. The first post contains more interesting content.</p><p>Don’t forget to read <a href="https://zine-ssg.io/docs/supermd/" target="_blank">the official SuperMD docs</a> to know how to <em>style</em> your content.</p><p>Btw this sample website also includes the JS/CSS dependencies required to render math:</p><script type="math/tex">\begin{aligned} -f(t) &= \int_{-\infty}^\infty F(\omega) \cdot (-1)^{2 \omega t} \mathrm{d}\omega \\ -F(\omega) &= \int_{-\infty}^\infty f(t) \div (-1)^{2 \omega t} \mathrm{d}t \\ -\end{aligned} -</script><p>This: <script type="math/tex">(-1)^x = \cos(\pi x) + i\sin(\pi x)</script> is an inline equation instead!</p> - https://example.com/post/second-post/ - Tue, 02 Jan 1990 00:00:00 +0000 - https://example.com/post/second-post/ - - - - First Post: What's A Zine? - <p>This is a sample first post for your blog.</p><p>This post is defined in <code>content/blog/first-post/</code> and contains the following files:</p><ul><li><code>index.smd</code></li><li><code>fanzine.jpg</code></li></ul><p>Another interesting thing about this post is that it uses the <code>layouts/post.shtml</code> template which adds at the bottom page navigation within the blog section.</p><p>Enough about Zine, let’s talk about zines.</p><h2>Fanzines</h2><p>A zine (short for <em>fanzine</em>, from “fan” + “magazine”) is a non-professional publication created by people that want to express themselves in paper form, usually in relation to a cultural phenomenon of some kind.</p><p><figure><img src="https://example.com/post/first-post/fanzine.jpg" alt="A photo of a black and white flyer. The main title reads 'A night of pure energy' and the rest of the page contains a mix of pictures of guitarists interleaved with other text snippets, some seemingly taken from a professional publication of some kind, and some handwritten to announce concert night dates, presumably for the musicians in the picture."> -<figcaption>An example of zine from 1976.</figcaption></figure></p><h2>Zines in the digital era</h2><p>In the digital age, some of the cultural impetus behind zines got redirected to personal blogs and similar digital, non-professional publications. The 90’s and early 00’s are famous for their whacky websites full of clip art, wordart text and “under construction” animated gifs.</p><p>This initial organic exploration didn’t last for too long as the rise of social media diverted a lot of self-expression energy towards walled gardens, a change that was also fueled by the much tighter and intense feedback loop that those platform enable.</p><p>In the 90’s the stongest dopamine hit you could get was adding a visit counter to your altavista website and watch it go up, slowly, mostly because of your own accesses. In modern times views are almost meaningless and interactions such as <em>likes</em>, <em>retweets</em> and <em>comments</em> provide much stronger positive feedback.</p><p>An unfortunate side effect of this new cultural wave centered around social media is that not only you end up gifting your content to platform owners, but you also participate in a system where the <em>language</em> of the social media site shapes your thoughts and experience in specific, and often user-hostile, ways.</p><p>Art, just like liquids, takes the shape of the container you put it in. A mobile game that lives off of in app purchases will never be truly great because of the tension between making the game entertaining enough to keep players engaged, and the need to make it boring enough so that they will want to buy upgrades to make the game more fun.</p><p>Similarly, self-expression on Twitter is encouraged to take the shape of short, hyperbolic hot takes that forgo nuance in order to create catchy quips that can be used for hasty decision making.</p><p>Likewise, <span class="wave ">corpowave</span> never goes out of style on LinkedIn. Spend enough time in there and you will become a character from Severance.</p><h2>We’re not in 1990 anymore</h2><p>Despite all the issues with social media, there is no point in thinking of the 90’s as a better time. It was not. And despite the winks at the past, Zine is not a tool for indulging in nostalgia.</p><p><strong>The goal is to make art</strong>: the act of inducing a change in others through our self-expression.</p><p>You could argue that the 90’s excelled at self-expression, but in doing so you would also have to accept that social media is infinitely more effective at inducing change in others (albeit at the expense of freedom of expression).</p><p>Once you realize that, the path forward is clear:</p><ol><li>Own your content.</li><li>Create new social systems that optimize for creating art over engagement.</li></ol><p>Owning your content means that you will be unaffected by enshittification of platforms that would otherwise keep your data hostage. It also is the single most effective thing you can do as an individual to take power away from platforms, all while protecting your own immediate interests.</p><p>Creation of new social systems is a <em>slightly more hairy</em> problem than self hosting a static website, but it’s something that can be done. Over the years we’ve had plenty of social outlets that have allowed people to socialize through their homemade games, music, drawings, fanfics, etc; and chances are that we have yet many more of these outlets ahead of us to create.</p><p>Zine gives you a small puzzle piece to help you inch closer toward a better future, partially by providing you with a new iteration over tried and true patterns (e.g. by facilitating content creation by separating content from layouting concerns as much as possible), and also by being a bit experimental with the concept of a devlog, something that you wouldn’t normally expect to find on a static website.</p><p>Lastly, Zine makes sure your content (both blog and devlog, but also any other content format you might come up with yourself) is available via RSS syndication. RSS feeds are far from a winning technology in the fight against the ebb and <em>enshitty</em>flow of social media, but they are another small puzzle piece that costs nothing to maintain and that might turn out to be critical once enough other preconditions are met.</p><p>With that in mind, <strong>go make art with your words</strong>.</p><p>– Loris</p> - https://example.com/post/first-post/ - Mon, 01 Jan 1990 00:00:00 +0000 - https://example.com/post/first-post/ - - - - diff --git a/public/post/second-post/index.html b/public/post/second-post/index.html deleted file mode 100644 index de54894..0000000 --- a/public/post/second-post/index.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - Zig 语言中文社区 - - - - - - - - - -
    -

    Zig 语言中文社区

    - -
    - -

    Second Post

    -

    This second post is mainly here to show you that you can also create single file posts for convenience. The first post contains more interesting content.

    Don’t forget to read the official SuperMD docs to know how to style your content.

    Btw this sample website also includes the JS/CSS dependencies required to render math:

    This: is an inline equation instead!

    - - - - - \ No newline at end of file diff --git a/public/style.css b/public/style.css deleted file mode 100644 index d4e5094..0000000 --- a/public/style.css +++ /dev/null @@ -1,47 +0,0 @@ -body { - margin: auto; - max-width: 60vw; - font-size: 1.5rem; - line-height: 1.5; -} - -a{ - color: inherit; -} - -.header { - display: flex; - justify-content: space-between; - align-items: center; -} -.site-title { - display: inline-block; - text-decoration: none; - color: inherit; -} -nav { - display: inline-block; -} -nav a { - text-decoration: none; - font-size: large; -} - -footer > div { - display: flex; - flex-direction: row; - justify-content: space-between; -} - -footer > div div { - margin: auto 0; -} - -footer > div i { - font-size: 24px; - margin: 0 5px; -} - -footer > div a { - text-decoration: none; -} diff --git a/public/zigcc.svg b/public/zigcc.svg deleted file mode 100644 index 85801dc..0000000 --- a/public/zigcc.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 05dea4f0d9725c8b45f8fe4bff9f197180c3a2ea Mon Sep 17 00:00:00 2001 From: xihale Date: Tue, 1 Jul 2025 22:10:19 +0800 Subject: [PATCH 12/22] Refactor markdown files to standardize headings and improve content structure. Update CSS styles for code highlighting to include additional languages and enhance maintainability. Adjust various sections in SMD files for consistency and clarity. --- TODO.md | 2 +- assets/highlight.css | 10 +++++-- content/about.smd | 4 +-- content/contributing.smd | 4 +-- content/learn/index.smd | 10 +++---- content/monthly/202209.smd | 4 +-- content/monthly/202308.smd | 24 +++++++-------- content/monthly/202309.smd | 4 +-- content/monthly/202405.smd | 4 +-- content/monthly/202406.smd | 8 ++--- content/monthly/202407.smd | 8 ++--- content/monthly/202410.smd | 24 +++++++-------- content/monthly/202411.smd | 8 ++--- content/post/0.14.smd | 16 +++++----- content/post/2023-09-05-bog-gc-1-en.smd | 14 ++++----- content/post/2023-09-05-bog-gc-1.smd | 14 ++++----- content/post/2023-09-05-hello-world.smd | 2 +- content/post/2023-09-21-zig-midi.smd | 26 ++++++++-------- .../2023-12-24-zig-build-explained-part1.smd | 20 ++++++------- .../2023-12-28-zig-build-explained-part2.smd | 30 +++++++++---------- .../2023-12-29-zig-build-explained-part3.smd | 30 +++++++++---------- content/post/2024-05-07-package-hash.smd | 14 ++++----- content/post/2024-08-12-zoop.smd | 18 +++++------ content/post/2024-11-26-typed-fsm.smd | 12 ++++---- content/post/2025-01-23-bonkers-comptime.smd | 18 +++++------ .../post/news/2023-12-27-second-meetup.smd | 10 +++---- 26 files changed, 171 insertions(+), 167 deletions(-) diff --git a/TODO.md b/TODO.md index 3d60897..4be3fe9 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,3 @@ -## 上游 Issue 计画 +# 上游 Issue 计画 - 标题不能包含 `\|` (无报错,但是 `BUILD ERROR`) \ No newline at end of file diff --git a/assets/highlight.css b/assets/highlight.css index e3a4697..1c8d961 100644 --- a/assets/highlight.css +++ b/assets/highlight.css @@ -10,9 +10,13 @@ --bracket: #93aa9e; } -code.zig, code.javascript{ +code { color: var(--code-fg); - .keyword, .keyword_modifier, .type_builtin, .keyword_type, .keyword_return, .keyword_conditional, .keyword_repeat, .keyword_operator, .constant_builtin, .keyword_exception{ +} + +code.zig, code.javascript, code.c, code.cpp, code.zon, code.go{ + color: var(--code-fg); + .keyword, .keyword_modifier, .type_builtin, .keyword_type, .keyword_return, .keyword_conditional, .keyword_repeat, .keyword_operator, .constant_builtin, .keyword_exception, .type{ color: var(--keyword); } .variable, .function_builtin{ @@ -30,7 +34,7 @@ code.zig, code.javascript{ .number{ color: var(--number); } - .keyword_function, .punctuation_delimiter{ + .keyword_function, .punctuation_delimiter, .function{ color: var(--function); } .punctuation_bracket{ diff --git a/content/about.smd b/content/about.smd index 3642810..3aaed07 100644 --- a/content/about.smd +++ b/content/about.smd @@ -6,7 +6,7 @@ .draft = false, --- -## About Zine +# About Zine Zine is an MIT-licensed project created by [Loris Cro](https://kristoff.it) and other contributors listed on the [official repository](https://github.com/kristoff-it/zine/contributors). @@ -34,7 +34,7 @@ of authoring languages: ># [NOTE]($block) >The correct file extension for SuperMD pages is `.smd`. -## Zine is alpha software +# Zine is alpha software Zine is not yet complete. The main functionality is present and you will be able to build even moderately complex static websites without issue. diff --git a/content/contributing.smd b/content/contributing.smd index 739cacc..0858c2a 100644 --- a/content/contributing.smd +++ b/content/contributing.smd @@ -26,13 +26,13 @@ date: '2023-09-05T16:13:13+0800' --- ``` -## 本地预览 +# 本地预览 TODO ```bash zine ``` -## 发布平台 +# 发布平台 - [ZigCC 网站](https://ziglang.cc) - [ZigCC 公众号](https://github.com/zigcc/.github/raw/main/zig_mp.png) diff --git a/content/learn/index.smd b/content/learn/index.smd index e73fe53..a8c39fa 100644 --- a/content/learn/index.smd +++ b/content/learn/index.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [《学习 Zig》 目录]($section.id('table-of-contents')) +# [《学习 Zig》 目录]($section.id('table-of-contents')) - [前言](./01-preface) - [安装 Zig](./02-installing-zig) @@ -24,7 +24,7 @@ 初次接触 Zig 的用户可以按序号依次阅读,对于有经验的 Zig 开发者可按需阅读感兴趣的章节。 -## 关于原作者 +# 关于原作者 [Karl Seguin](https://www.linkedin.com/in/karlseguin/) 在多个领域有着丰富经验,前微软 MVP,他撰写了大量文章,是多个微软公共新闻组的活跃成员。现居新加坡。他还是以下教程的作者: @@ -34,13 +34,13 @@ 可以在 找到他的博客,或者通过 [@karlseguin](http://twitter.com/karlseguin) 在 Twitter 上关注他。 -## 翻译原则 +# 翻译原则 技术文档的翻译首要原则是准确,但在准确的前提下如何保证『信、达、雅』?这是个挑战,在翻译本教程时,在某些情况下会根据上下文进行意译,便于中文读者阅读。 最后,感谢翻译者的无私贡献。❤️️ -## 离线阅读 +# 离线阅读 在本仓库的 [release 页面](https://github.com/zigcc/zigcc.github.io/releases)会定期将本教程导出为 PDF 格式,读者可按需下载。 @@ -49,7 +49,7 @@ ``` -## 其他学习资料 +# 其他学习资料 由于 Zig 目前还处于快速迭代,因此最权威的资料无疑是官方的 [Zig Language Reference](https://ziglang.org/documentation/master/),遇到语言的细节问题,基本都可以在这里找到答案。 其次是社区的一些高质量教程,例如: diff --git a/content/monthly/202209.smd b/content/monthly/202209.smd index c72c163..37db874 100644 --- a/content/monthly/202209.smd +++ b/content/monthly/202209.smd @@ -16,7 +16,7 @@ Let us exist。这里稍微整理下这件事情的过程: 本次事件主要 - Rust 核心贡献者: Patrick Walton - Zig 社区 VP: Loris Cro -### 时间线 +## 时间线 - 8/26 号,一篇关于 wasm 2 Game Jam 的[分析报告](https://wasm4.org/blog/jam-2-results/)中,使用 Zig @@ -57,7 +57,7 @@ Let us exist。这里稍微整理下这件事情的过程: 本次事件主要 这里[提到的](https://github.com/tigerbeetledb/tigerbeetle/blob/main/docs/DESIGN.md)。而且即便内存安全,也可能发生 OOM -### 总结 +## 总结 上面的链接比较多,这里稍微总结下这次争论的问题: diff --git a/content/monthly/202308.smd b/content/monthly/202308.smd index 57a41c9..f00cb42 100644 --- a/content/monthly/202308.smd +++ b/content/monthly/202308.smd @@ -11,7 +11,7 @@ 0.11 终于在 8 月 4 号释出了,下面来看看它的一些重要改进吧。[HN 讨论](https://news.ycombinator.com/item?id=36995735) -## Peer Type Resolution Improvements +# Peer Type Resolution Improvements 对等类型解析算法得到改进,下面是一些在 0.10 中不能解析,但在 0.11 中可以解析的例子: @@ -29,7 +29,7 @@ 而且现在使用 `@intCast` 这类 builtin 都只接受一个参数,目前类型根据上下文自动推断出来。 -## Multi-Object For Loops +# Multi-Object For Loops 可以同时对多个对象进行遍历: @@ -45,7 +45,7 @@ for (input, 0..) |x, i| { } ``` -## @min and @max +# @min and @max 主要有两个改动: @@ -65,7 +65,7 @@ test "@min/@max refines result type" { } ``` -## @inComptime +# @inComptime 新加的 builtin,用于判断执行是否在 comptime 环境下执行: @@ -95,12 +95,12 @@ test "@inComptime" { } ``` -## 类型转化相关 builtin 的重命名 +# 类型转化相关 builtin 的重命名 之前 `@xToY` 形式的 builtin 现在已经改成了 `@yFromX` ,这样的主要好处是便于阅读(从右向左),这是[草案](https://github.com/ziglang/zig/issues/6128)。 -## Tuple 类型声明 +# Tuple 类型声明 现在可以直接用无 field 名字的 struct 来声明 tuple 类型: @@ -133,7 +133,7 @@ test "tuple declarations" { + const testcases = [_]struct { []const u8, []const u8, bool }{ ``` -## 排序 +# 排序 现在排序算法分布两类: @@ -143,7 +143,7 @@ test "tuple declarations" { 与堆排的快速最差情况相结合,同时在具有特定模式的输入上达到线性时间。 -## Stack Unwinding +# Stack Unwinding Zig 之前依赖 [frame pointer](https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers) @@ -154,20 +154,20 @@ pointer](https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers) DWARF unwind tables 和 MachO compact unwind information 来会恢复堆栈,详见:[\#15823](https://github.com/ziglang/zig/pull/15823)。 -## 包管理 +# 包管理 0.11 首次正式引入了包管理器,具体解释可以参考:[Zig Build System](https://en.liujiacai.net/2023/04/13/zig-build-system/),而且很重要一点,step 之间可以[并发执行](https://ziglang.org/download/0.11.0/release-notes.html#Steps-Run-In-Parallel), -## Bootstrapping +# Bootstrapping C++ 实现的 Zig 编译器已经被彻底移除,这意味着 `-fstage1` 不再生效,Zig 现在只需要一个 2.4M 的 WebAssembly 文件和一个 C 编辑器即可,工作细节可以参考:[Goodbye to the C++ Implementation of Zig](https://ziglang.org/news/goodbye-cpp/)。 -## 代码生成 +# 代码生成 虽然 Zig 编译器现在还是主要使用 LLVM 来进行代码生成,但在这次发布中,其他几个后端也有了非常大的进步: @@ -182,7 +182,7 @@ Zig](https://ziglang.org/news/goodbye-cpp/)。 后端专注于为 OpenCL 内核生成代码,不过未来可能也会支持兼容 Vulkan 的着色器。 -## 增量编译 +# 增量编译 虽然这仍是一个高度 WIP 的功能,但这一版本周期中的许多改进为编译器的增量编译功能铺平了道路。其中最重要的是 diff --git a/content/monthly/202309.smd b/content/monthly/202309.smd index 82de0f5..c5afb6f 100644 --- a/content/monthly/202309.smd +++ b/content/monthly/202309.smd @@ -8,7 +8,7 @@ # 重大事件 -## [Bounties Damage Open Source Projects](https://ziglang.org/news/bounties-damage-open-source-projects/) +# [Bounties Damage Open Source Projects](https://ziglang.org/news/bounties-damage-open-source-projects/) 在 2023-09-11 号,Wasmerio CEO 创建了 [Support WASIX · Issue \#17115](https://github.com/ziglang/zig/issues/17115),表示想赞助 Zig @@ -23,7 +23,7 @@ Andrew 与 Loris 在这篇文章中主要阐述了这么做为什么是伤害社 社区,他更想保证 Zig 的独立性,这也是他们创办 [Software You Can Love](https://kristoff.it/blog/the-open-source-game/) 的初衷。 -## [Bun 1.0](https://bun.sh/blog/bun-v1.0) +# [Bun 1.0](https://bun.sh/blog/bun-v1.0) 面包终于出炉了!Bun 毫无疑问是 Zig 的明星项目,它在 2023-09-08 正式发布了 1.0 版本,这对开发者来说可能没什么太大的区别,毕竟就只是个 diff --git a/content/monthly/202405.smd b/content/monthly/202405.smd index ea8df91..d12b346 100644 --- a/content/monthly/202405.smd +++ b/content/monthly/202405.smd @@ -8,12 +8,12 @@ # 观点/教程 -## [Thoughts on Zig](https://arne.me/blog/thoughts-on-zig) +# [Thoughts on Zig](https://arne.me/blog/thoughts-on-zig) 又一篇 Zig 初学者的使用体验文档,如果你也在犹豫要不要学 Zig,这是个不错的经验参考。 -## [I'm sold on Zig's simplicity : r/Zig](https://www.reddit.com/r/Zig/comments/1ckstjv/im_sold_on_zigs_simplicity/) +# [I'm sold on Zig's simplicity : r/Zig](https://www.reddit.com/r/Zig/comments/1ckstjv/im_sold_on_zigs_simplicity/) 一个具有资深经验开发者,在这里描述了自己选择业余项目语言的经历: diff --git a/content/monthly/202406.smd b/content/monthly/202406.smd index c649c59..b3c0de5 100644 --- a/content/monthly/202406.smd +++ b/content/monthly/202406.smd @@ -31,7 +31,7 @@ const map = std.StaticStringMap(T).initComptime(kvs_list); # 观点/教程 -## [Leveraging Zig's Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/) +# [Leveraging Zig's Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/) 老朋友 openmymind 的又一篇好文章:如何利用 Zig 的 Allocator 来实现请求级别的内存分配。 Zig Allocator @@ -100,7 +100,7 @@ fn run(worker: *Worker) void { } ``` -## [On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/) +# [On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/) - @@ -131,14 +131,14 @@ Rust。 重写的项目运行在多个平台上(Web、移动端、VR 相信这也是大部分人选择 Zig 的原因:简洁、高效。 -## [Packing some Zig before going for the countryside](https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/) +# [Packing some Zig before going for the countryside](https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/) 作者列举的一些 Zig 学习资料、常用类库。该作者的另一篇文章也有不少资料:[2024 Collection of Zig resources](https://ludwigabap.bearblog.dev/2024-collection-of-zig-resources/) -## [Why I am not yet ready to switch to Zig from Rust](https://turso.tech/blog/why-i-am-not-yet-ready-to-switch-to-zig-from-rust) +# [Why I am not yet ready to switch to Zig from Rust](https://turso.tech/blog/why-i-am-not-yet-ready-to-switch-to-zig-from-rust) Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较喜欢 C,但 C 不是一门安全的语言,因此通过 Rust,作者可以避免 写出 SIGSEGVS diff --git a/content/monthly/202407.smd b/content/monthly/202407.smd index 5890281..61974c1 100644 --- a/content/monthly/202407.smd +++ b/content/monthly/202407.smd @@ -36,7 +36,7 @@ Zig 开发者的一些观点: # 观点/教程 -## [Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/) +# [Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/) Loris Cro 的最新文章,介绍了一个改进 Zig 编码体验的小技巧,十分推荐大家使用。具体来说是这样的: 通过配置 @@ -82,7 +82,7 @@ zls --config-path zls.json 这样不同的项目就可以用不同的检查步骤了。 -## [Systems Distributed '24](https://guergabo.substack.com/p/systems-distributed-24) +# [Systems Distributed '24](https://guergabo.substack.com/p/systems-distributed-24) 作者对这次会议的一个回顾总结,议题主要有如下几个方向: @@ -92,7 +92,7 @@ zls --config-path zls.json - Lessons from Building Distributed Databases - Notes from Water Cooler Chats -## [C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself](https://jstrieb.github.io/posts/c-reflection-zig/) +# [C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself](https://jstrieb.github.io/posts/c-reflection-zig/) 该作者分享了利用 typeInfo 来在编译时获取字段名的能力,要知道,在 C 里面是没有这个功能的。 @@ -135,7 +135,7 @@ fn get_window_messages() [65536][:0]const u8 { } ``` -## [A TypeScripter's Take on Zig (Advent of Code 2023)](https://effectivetypescript.com/2024/07/17/advent2023-zig/) +# [A TypeScripter's Take on Zig (Advent of Code 2023)](https://effectivetypescript.com/2024/07/17/advent2023-zig/) 以下该作者的一些心得体会: diff --git a/content/monthly/202410.smd b/content/monthly/202410.smd index 0e844c3..22258f7 100644 --- a/content/monthly/202410.smd +++ b/content/monthly/202410.smd @@ -8,7 +8,7 @@ # 重大事件 -## 向 Zig 软件基金会认捐 30 万美元 +# 向 Zig 软件基金会认捐 30 万美元 Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金会 (ZSF) 捐赠了 300,000 美元。 @@ -27,7 +27,7 @@ Zig 就是这样一个项目。 # 观点/教程 -## [Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/) +# [Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/) 对 Zig 的特色进行了简单扼要的介绍,主要有: @@ -61,9 +61,9 @@ Zig 就是这样一个项目。 4. 与 C 无缝交互, `zig cc` 是交叉编译的首选 -## [Building Nintendo 3DS Homebrew with Zig](https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html) +# [Building Nintendo 3DS Homebrew with Zig](https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html) -## [Critical Social Infrastructure for Zig Communities | Loris Cro's Blog](https://kristoff.it/blog/critical-social-infrastructure/) +# [Critical Social Infrastructure for Zig Communities | Loris Cro's Blog](https://kristoff.it/blog/critical-social-infrastructure/) 对于一个试图共同学习如何制作大家都喜欢的软件的社区来说,能够分享想法并开展合作至关重要,但社交平台的不断起伏会导致连接中断,这对于一个从一开始就希望去中心化的社区来说是个大问题。 @@ -74,23 +74,23 @@ Zig 就是这样一个项目。 - - -## [The Zig Website Has Been Re-engineered](https://ziglang.org/news/website-zine/) +# [The Zig Website Has Been Re-engineered](https://ziglang.org/news/website-zine/) Zig 官网已经用 [Zine](https://zine-ssg.io/) 重写! -## [Rust vs. Zig in Reality: A (Somewhat) Friendly Debate](https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/) +# [Rust vs. Zig in Reality: A (Somewhat) Friendly Debate](https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/) -## [Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun](https://kevinlynagh.com/rust-zig/) +# [Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun](https://kevinlynagh.com/rust-zig/) -## [Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig](https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html) +# [Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig](https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html) -## 视频 +# 视频 -### [I made an operating system that self replicates doom on a network “from scratch”](https://www.youtube.com/watch?v=xOySJpQlmv4&feature=youtu.be) +## [I made an operating system that self replicates doom on a network “from scratch”](https://www.youtube.com/watch?v=xOySJpQlmv4&feature=youtu.be) -### [Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature=shared&v=3fWx5BOiUiY) +## [Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature=shared&v=3fWx5BOiUiY) -### [Let's explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764) +## [Let's explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764) # 项目/工具 diff --git a/content/monthly/202411.smd b/content/monthly/202411.smd index 247f039..75f5881 100644 --- a/content/monthly/202411.smd +++ b/content/monthly/202411.smd @@ -8,7 +8,7 @@ # 观点/教程 -## [Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html) +# [Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html) [JAM](https://github.com/srijan-paul/jam) 作者写的一篇文章,分析里市面上现有的 JS @@ -48,7 +48,7 @@ esquery 的问题在于执行效率,通过借助于 zig 的 comptime 来在编译器翻译 esquery 就避免了运行时开销。 -## [Advent of Code in Zig | Loris Cro's Blog](https://kristoff.it/blog/advent-of-code-zig/) +# [Advent of Code in Zig | Loris Cro's Blog](https://kristoff.it/blog/advent-of-code-zig/) 一年一度的 AoC 又到了,这篇文章里给出了一些使用的技巧来帮助大家用 Zig 来解决 AoC 问题。 @@ -99,9 +99,9 @@ > 的某些功能甚至能帮助您取得比其他语言更快的进展,但它最终还是针对软件工程进行了优化,而这并不是您在 > AoC 中要做的事情。 -## [Zig and Emulators](https://floooh.github.io/2024/08/24/zig-and-emulators.html) +# [Zig and Emulators](https://floooh.github.io/2024/08/24/zig-and-emulators.html) -## [Zig Reproduced Without Binaries](https://jakstys.lt/2024/zig-reproduced-without-binaries/) +# [Zig Reproduced Without Binaries](https://jakstys.lt/2024/zig-reproduced-without-binaries/) 一个很有趣的实验,在 0.10 版本中,Zig 编译器实现了自举,即可以用老版本的 Zig 来编译 Zig 源码,生成最新的 Zig diff --git a/content/post/0.14.smd b/content/post/0.14.smd index ff19cda..9ba4469 100644 --- a/content/post/0.14.smd +++ b/content/post/0.14.smd @@ -126,11 +126,11 @@ Zig 0.14.0 版本是经过 **9 个月的工作**,由 **251 位不同的贡献 # 关于 Zig 0.14.0 版本的常见问题解答 -## Zig 0.14.0 版本的主要更新和亮点是什么? +# Zig 0.14.0 版本的主要更新和亮点是什么? Zig 0.14.0 版本是长达 9 个月开发工作和 3467 次提交的成果,主要亮点包括:显著增强了对多种目标平台的支持,包括 arm/thumb、mips/mips64、powerpc/powerpc64、riscv32/riscv64 和 s390x 等,许多之前存在工具链问题、标准库支持缺失或崩溃的情况现在应该可以正常工作了。此外,该版本在构建系统方面进行了大量升级,并对语言进行了多项重要改进,例如引入了 Labeled Switch 和 Decl Literals 等新特性。为了缩短编辑/编译/调试周期,版本还迈向了两个长期投资目标:增量编译和快速 x86 后端。 -## Zig 如何对不同目标平台的开发支持进行分级? +# Zig 如何对不同目标平台的开发支持进行分级? Zig 使用四层系统来对不同目标平台的支持级别进行分类,其中 Tier 1 是最高级别: @@ -139,15 +139,15 @@ Zig 使用四层系统来对不同目标平台的支持级别进行分类,其 - **Tier 3:** 编译器可以依赖外部后端(如 LLVM)为该目标平台生成机器代码。链接器可以为该目标平台生成目标文件、库和可执行文件。 - **Tier 4:** 编译器可以依赖外部后端(如 LLVM)为该目标平台生成汇编源代码。如果 LLVM 将此目标平台视为实验性,则需要从源代码构建 LLVM 和 Zig 才能使用它。 -## 什么是 Labeled Switch,它有什么优势? +# 什么是 Labeled Switch,它有什么优势? Labeled Switch 是 Zig 0.14.0 中引入的一项语言特性,允许 switch 语句被标记,并作为 continue 语句的目标。continue :label value 语句会用 value 替换原始的 switch 表达式操作数,并重新评估 switch。尽管在语义上类似于循环中的 switch,但 Labeled Switch 的关键优势在于其代码生成特性。它可以生成帮助 CPU 更准确预测分支的代码,从而提高热循环中的性能,特别是在处理指令分派、评估有限状态自动机 (FSA) 或执行类似基于 case 的评估时。这有助于 branch predictor 更准确地预测控制流。 -## Decl Literals 是什么,它解决了哪些问题? +# Decl Literals 是什么,它解决了哪些问题? Decl Literals 是 Zig 0.14.0 扩展 "enum literal" 语法 (.foo) 而引入的新特性。现在,一个枚举字面量 .foo 不仅可以引用枚举变体,还可以使用 Result Location Semantics 引用目标类型上的任何声明(const/var/fn)。这在初始化结构体字段时特别有用,可以避免重复指定类型,并有助于避免 Faulty Default Field Values 的问题,确保数据不变量不会因覆盖单个字段而受到破坏。它也支持直接调用函数来初始化值。 -## Zig 0.14.0 版本在内存分配器方面有哪些值得关注的变化? +# Zig 0.14.0 版本在内存分配器方面有哪些值得关注的变化? 该版本对内存分配器进行了多项改进: @@ -156,7 +156,7 @@ Decl Literals 是 Zig 0.14.0 扩展 "enum literal" 语法 (.foo) 而引入的新 - **Allocator API Changes (remap):** std.mem.Allocator.VTable 引入了一个新的 remap 函数,允许尝试扩展或收缩内存并可能重新定位,如果无法在不执行内部 memcpy 的情况下完成,则返回 null,提示调用者自行处理复制。同时,resize 函数保持不变。Allocator.VTable 中的所有函数现在使用 std.mem.Alignment 类型代替 u8,增加了类型安全。 - **Runtime Page Size:** 移除了编译时已知的 std.mem.page\_size,代之以编译时已知的页面大小上下界 std.heap.page\_size\_min 和 std.heap.page\_size\_max。运行时获取页面大小可以使用 std.heap.pageSize(),它会优先使用编译时已知的值,否则在运行时查询操作系统并缓存结果。这修复了对 Asahi Linux 等新硬件上运行 Linux 的支持。 -## Zig 0.14.0 版本如何改进构建系统,特别是处理模块和依赖关系? +# Zig 0.14.0 版本如何改进构建系统,特别是处理模块和依赖关系? Zig 0.14.0 版本在构建系统方面有多项重要改进: @@ -166,7 +166,7 @@ Zig 0.14.0 版本在构建系统方面有多项重要改进: - **addLibrary Function:** 引入 addLibrary 函数作为 addSharedLibrary 和 addStaticLibrary 的替代,允许在 build.zig 中更容易地切换链接模式,并与 linkLibrary 函数名称保持一致。 - **Import ZON:** ZON 文件现在可以在编译时通过 @import("foo.zon") 导入,前提是结果类型已知。 -## Zig 0.14.0 版本在编译器后端和编译速度方面有哪些进展? +# Zig 0.14.0 版本在编译器后端和编译速度方面有哪些进展? 该版本在编译器后端和编译速度方面取得了进展: @@ -174,7 +174,7 @@ Zig 0.14.0 版本在构建系统方面有多项重要改进: - **Incremental Compilation:** 引入了增量编译特性,可以通过 -fincremental 标志启用。尽管尚未默认启用,但结合文件系统监听,可以显著缩短修改代码后的重新分析时间,提供快速的编译错误反馈。 - **x86 Backend:** x86 后端在行为测试套件中的通过率已接近 LLVM 后端,并且在开发时通常比 LLVM 后端提供更快的编译速度和更好的调试器支持。虽然尚未默认选中,但鼓励用户尝试使用 -fno-llvm 或在构建脚本中设置 use\_llvm = false 来启用。 -## Zig 0.14.0 版本在工具链和运行时方面有哪些值得注意的更新? +# Zig 0.14.0 版本在工具链和运行时方面有哪些值得注意的更新? 该版本在工具链和运行时方面也有多项更新: diff --git a/content/post/2023-09-05-bog-gc-1-en.smd b/content/post/2023-09-05-bog-gc-1-en.smd index 1f3c43b..19b8056 100644 --- a/content/post/2023-09-05-bog-gc-1-en.smd +++ b/content/post/2023-09-05-bog-gc-1-en.smd @@ -14,7 +14,7 @@ Bog is a small scripting language developed using Zig. Its GC design is inspired by a paper titled [An efficient of Non-Moving GC for Function languages](https://www.pllab.riec.tohoku.ac.jp/papers/icfp2011UenoOhoriOtomoAuthorVersion.pdf). -## Overview +# Overview 1. Introduction - Design of the Heap @@ -22,7 +22,7 @@ Bog is a small scripting language developed using Zig. Its GC design is inspired - Design of Bitmap 2. Implementation -## Introduction +# Introduction GC stands for garbage collection, which is primarily a memory management strategy for the `heap` region. Memory allocations in the heap are done in exponentially increasing sizes, with a special sub-heap dedicated solely to very large objects. One advantage of this approach might be its efficiency in handling memory requests of various sizes. @@ -71,13 +71,13 @@ graph TD +------------+ ``` -### Memory Sub-Heap Pool +## Memory Sub-Heap Pool We've designed a memory resource pool. This pool consists of numerous allocation segments of fixed size. It means that, regardless of how much space a sub-heap requests, it will request it in units of these fixed-size "segments". For instance, if the allocation segments in the pool are of size 1MB, then a sub-heap might request space in sizes of 1MB, 2MB, 3MB, etc., rather than requesting non-integer multiples like 1.5MB or 2.5MB. The dynamic allocation and reclaiming of space by the sub-heaps from a resource pool made up of fixed-size segments provide greater flexibility and may enhance the efficiency of memory utilization. -### Types of GC +## Types of GC There are many common types of GC. @@ -136,7 +136,7 @@ However, Non-Moving GC has its disadvantages: To address these problems requires many complicated steps, which won't be elaborated on here. We'll focus on Bog's GC for the explanation. -### Meta Bitmap +## Meta Bitmap "Meta Bitmap" or "meta-level bitmaps". This is a higher-level bitmap that summarizes the contents of the original bitmap. This hierarchical structure is similar to the inode mapping in file systems or the use of multi-level page tables in computer memory management. @@ -150,11 +150,11 @@ Let's design for a 32-bit architecture. A 32-bit architecture means that the com For example, if a bitmap is 320 bits long, then on a 32-bit architecture, the worst-case scenario might require checking 10 blocks of 32 bits to find a free bit. This can be represented by log32(320), which results in 10. -### Bitmap +## Bitmap Since Bog's GC is essentially still based on "Mark-Sweep", using bitmaps to record data is indispensable. In Bog, we adopted the method of "bitmap records data" for GC. And to improve efficiency, we introduced the concept of meta-bitmaps, where every 4 elements correspond to a meta-bitmap, recording the occupancy status of multiple spaces, and increasing the depth based on the object age in the heap. -### Implementation +## Implementation In reality, Bog's design is a bit more complex. Here are sample in practical code: diff --git a/content/post/2023-09-05-bog-gc-1.smd b/content/post/2023-09-05-bog-gc-1.smd index a06194e..8e1748d 100644 --- a/content/post/2023-09-05-bog-gc-1.smd +++ b/content/post/2023-09-05-bog-gc-1.smd @@ -14,7 +14,7 @@ [Bog](https://github.com/Vexu/bog) 是一款基于 Zig 开发的小型脚本语言。它的 GC 设计受到一篇论文[An efficient of Non-Moving GC for Function languages](https://www.pllab.riec.tohoku.ac.jp/papers/icfp2011UenoOhoriOtomoAuthorVersion.pdf)的启发。 -## 梗概 +# 梗概 1. 概述 - Heap 的设计 @@ -22,7 +22,7 @@ - Bitmap 的设计 2. 实现 -## 概述 +# 概述 GC 是一种垃圾回收的机制,主要是针对`heap`区域的内存管理策略。在堆中的内存分配是按照指数级增长的大小进行的,此外还有一个专门用于非常大对象的特殊子堆。这种方法的一个优点可能是它可以高效地处理各种大小的内存请求。 @@ -71,13 +71,13 @@ graph TD +------------+ ``` -### 内存子堆池 +## 内存子堆池 我们设计一个内存的资源池。这个池由许多固定大小的分配段组成。这意味着,无论子堆请求多少空间,它都会以这些固定大小的“段”为单位来请求。例如,如果池中的分配段大小为 1MB,那么一个子堆可能会请求 1MB、2MB、3MB 等大小的空间,而不是请求 1.5MB 或 2.5MB 这样的非整数倍的大小。 而子堆从一个由固定大小的段组成的资源池中动态分配和回收空间,这种策略可以提供更高的灵活性,并可能提高内存使用的效率。 -### GC 的类别 +## GC 的类别 常见的 GC 有很多类型。 @@ -131,7 +131,7 @@ Cheney 的拷贝收集器: 是一种用于半区(semi-space)的拷贝垃圾 为了解决这些问题需要很多复杂的步骤,在此不多赘述。单以 Bog 的 GC 来讲解。 -### Meta Bitmap +## Meta Bitmap “元级位图”或“meta-level bitmaps”。这是一个更高级别的位图,用于汇总原始位图的内容。这种层次化的结构类似于文件系统中的 inode 映射或多级页表在计算机内存管理中的使用。 @@ -145,11 +145,11 @@ Cheney 的拷贝收集器: 是一种用于半区(semi-space)的拷贝垃圾 例如,如果一个位图有 320 位,那么在 32 位架构上,最坏的情况可能需要检查 10 个 32 位块才能找到一个空闲位。这可以通过 $\log_{32}(320)$来表示,结果是 10。 -### Bitmap +## Bitmap 由于 Bog 的 GC 本质上还是采用了“标记-清除”,所以利用位图来记录数据是必不可少的。在 Bog 中,我们采用了“位图记录数据”的方式来进行 GC。而为了提高效率,我们增加了元位图的概念,即每 4 个元素对应一个元位图,用于记录多空间的占用状态,并且根据 heap 的对象时间增加深度。 -### 实现 +## 实现 实际上,在 Bog 的设计中,要更加复杂一些。我们增加了 diff --git a/content/post/2023-09-05-hello-world.smd b/content/post/2023-09-05-hello-world.smd index efc0041..3a4b8f1 100644 --- a/content/post/2023-09-05-hello-world.smd +++ b/content/post/2023-09-05-hello-world.smd @@ -27,6 +27,6 @@ date: '2023-09-05T16:13:13+0800' --- ``` -## 本地预览 +# 本地预览 在写完文章后,可以使用 [Hugo](https://gohugo.io/) 进行本地预览,只需在项目根目录执行 `hugo server`,这会启动一个 HTTP 服务,默认的访问地址是: http://localhost:1313/ diff --git a/content/post/2023-09-21-zig-midi.smd b/content/post/2023-09-21-zig-midi.smd index 84d58c2..3e91fbf 100644 --- a/content/post/2023-09-21-zig-midi.smd +++ b/content/post/2023-09-21-zig-midi.smd @@ -23,7 +23,7 @@ MIDI 是“乐器数字接口”的缩写,是一种用于音乐设备之间通 ├── midi.zig ``` -## 基础 +# 基础 在 MIDI 协议中,`0xFF` 是一个特定的状态字节,用来表示元事件(Meta Event)的开始。元事件是 MIDI 文件结构中的一种特定消息,通常不用于实时音频播放,但它们包含有关 MIDI 序列的元数据,例如序列名称、版权信息、歌词、时间标记、速度(BPM)更改等。 @@ -56,7 +56,7 @@ MIDI 是“乐器数字接口”的缩写,是一种用于音乐设备之间通 元事件主要存在于 MIDI 文件中,特别是在标准 MIDI 文件 (SMF) 的上下文中。在实时 MIDI 通信中,元事件通常不会被发送,因为它们通常不会影响音乐的实际播放。 -## Midi.zig +# Midi.zig 本文件主要是处理 MIDI 消息的模块,为处理 MIDI 消息提供了基础结构和函数。 @@ -194,7 +194,7 @@ pub const Message = struct { - value 和 setValue 函数:用于获取和设置 MIDI 消息的值字段。 - Kind 枚举:定义了 MIDI 消息的所有可能种类,包括通道事件和系统事件。 -### midi 消息结构 +## midi 消息结构 我们需要先了解 MIDI 消息的一些背景。 @@ -211,7 +211,7 @@ pub const Message = struct { 4. **设置值**:`message.values = .{ ... }` 将这两个 7 位的值设置到 `message.values` 中。 -### 事件 +## 事件 针对事件,我们看 enum。 @@ -251,7 +251,7 @@ pub const Message = struct { 以下是对每个枚举成员的简要说明: -#### 频道事件 (Channel events) +### 频道事件 (Channel events) 1. **NoteOff**:这是一个音符结束事件,表示某个音符不再播放。 2. **NoteOn**:这是一个音符开始事件,表示开始播放某个音符。 @@ -261,7 +261,7 @@ pub const Message = struct { 6. **ChannelPressure**:频道压力事件,与多声道键盘压力相似,但它适用于整个频道,而不是特定音符。 7. **PitchBendChange**:音高弯曲变更事件,表示音符音高的上升或下降。 -#### 系统事件 (System events) +### 系统事件 (System events) 1. **ExclusiveStart**:独占开始事件,标志着一个独占消息序列的开始。 2. **MidiTimeCodeQuarterFrame**:MIDI 时间码四分之一帧,用于同步与其他设备。 @@ -276,11 +276,11 @@ pub const Message = struct { 11. **ActiveSensing**:活动感知事件,是一种心跳信号,表示设备仍然在线并工作。 12. **Reset**:重置事件,用于将设备重置为其初始状态。 -#### 其他 +### 其他 1. **Undefined**:未定义事件,可能表示一个未在此枚举中定义的或无效的 MIDI 事件。 -## decode.zig +# decode.zig 本文件是对 MIDI 文件的解码器, 提供了一组工具,可以从不同的输入源解析 MIDI 文件的各个部分。这样可以方便地读取和处理 MIDI 文件。 @@ -498,7 +498,7 @@ pub fn file(reader: anytype, allocator: *mem.Allocator) !midi.File { 8. trackEvent: 从 reader 中解析一个 MIDI 轨道事件。它可以是 MIDI 消息或元事件。 9. file: 用于从 reader 解码一个完整的 MIDI 文件。它首先解码文件头,然后解码所有的文件块。这个函数会返回一个表示 MIDI 文件的结构体。 -### message 解析 +## message 解析 ```zig const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { @@ -531,7 +531,7 @@ const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { 4. `else return error.InvalidMessage;`: 如果 `first_byte` 不是状态字节,并且不存在前一个消息,那么返回一个 `InvalidMessage` 错误。 -## encode.zig +# encode.zig 本文件用于将 MIDI 数据结构编码为其对应的二进制形式。具体来说,它是将内存中的 MIDI 数据结构转换为 MIDI 文件格式的二进制数据。 @@ -672,7 +672,7 @@ pub fn file(writer: anytype, f: midi.File) !void { - file 函数:这是主函数,用于将整个 MIDI 文件数据结构编码为其二进制形式。它首先编码文件头,然后循环编码每个块和块中的事件。 -### int 函数 +## int 函数 ```zig @@ -731,7 +731,7 @@ pub fn int(writer: anytype, i: u28) !void { - 使用提供的`writer`将翻转后的字节写入到目标位置。 -## file.zig +# file.zig 主要目的是为了表示和处理 MIDI 文件的不同部分,以及提供了一个迭代器来遍历 MIDI 轨道的事件。 @@ -912,7 +912,7 @@ pub const TrackIterator = struct { - 定义了一个 Result 结构来返回事件和关联的数据。 - 提供了一个`next`方法来读取下一个事件。 -## Build.zig +# Build.zig buid.zig 是一个 Zig 构建脚本(build.zig),用于配置和驱动 Zig 的构建过程。 diff --git a/content/post/2023-12-24-zig-build-explained-part1.smd b/content/post/2023-12-24-zig-build-explained-part1.smd index 1baf9b2..72a68f1 100644 --- a/content/post/2023-12-24-zig-build-explained-part1.smd +++ b/content/post/2023-12-24-zig-build-explained-part1.smd @@ -15,11 +15,11 @@ Zig 构建系统仍然缺少文档,对很多人来说,这是不使用它的 我们将从一个刚刚初始化的 Zig 项目开始,逐步深入到更复杂的项目。在此过程中,我们将学习如何使用库和软件包、添加 C 代码,甚至如何创建自己的构建步骤。 -## 免责声明 +# 免责声明 由于我不会解释 Zig 语言的语法或语义,因此我希望你至少已经有了一些使用 Zig 的基本经验。我还将链接到标准库源代码中的几个要点,以便您了解所有这些内容的来源。我建议你阅读编译系统的源代码,因为如果你开始挖掘编译脚本中的函数,大部分内容都不言自明。所有功能都是在标准库中实现的,不存在隐藏的构建魔法。 -## 开始 +# 开始 我们通过新建一个文件夹来创建一个新项目,并在该文件夹中调用 zig init-exe。 @@ -56,7 +56,7 @@ pub fn build(b: *std.Build) void { } ``` -## 基础知识 +# 基础知识 构建系统的核心理念是,Zig 工具链将编译一个 Zig 程序 (build.zig),该程序将导出一个特殊的入口点(`pub fn build(b: *std.build.Builder) void`),当我们调用 `zig build` 时,该入口点将被调用。 @@ -95,7 +95,7 @@ Step 遵循与 std.mem.Allocator 相同的接口模式,需要实现一个 make 现在,我们需要创建一个稍正式的 Zig 程序: -## 编译 Zig 源代码 +# 编译 Zig 源代码 要使用编译系统编译可执行文件,编译器需要使用函数 Builder.addExecutable,它将为我们创建一个新的 LibExeObjStep。这个步骤实现是 zig build-exe、zig build-lib、zig build-obj 或 zig test 的便捷封装,具体取决于初始化方式。本文稍后将对此进行详细介绍。 @@ -118,7 +118,7 @@ pub fn build(b: *std.build.Builder) void { 这将始终在当前机器的调试模式下编译,因此对于初学者来说,这可能就足够了。但如果你想开始发布你的项目,你可能需要启用交叉编译: -## 交叉编译 +# 交叉编译 交叉编译是通过设置程序的目标和编译模式来实现的 @@ -163,7 +163,7 @@ pub fn build(b: *std.build.Builder) void { 现在,如果你调用 zig build --help 命令,就会在输出中看到以下部分,而之前这部分是空的: -``` +```bash Project-Specific Options: -Dtarget=[string] The CPU architecture, OS, and ABI to build for -Dcpu=[string] Target CPU features to add or subtract @@ -187,7 +187,7 @@ zig build -Doptimize=ReleaseSmall 但我们仍然必须调用 zig build 编译,因为默认调用仍然没有任何作用。让我们改变一下! -## 安装工件 +# 安装工件 要安装任何东西,我们必须让它依赖于构建器的安装步骤。该步骤是已创建的,可通过 Builder.getInstallStep() 访问。我们还需要创建一个新的 InstallArtifactStep,将我们的 exe 文件复制到安装目录(通常是 zig-out) @@ -254,7 +254,7 @@ pub fn build(b: *std.build.Builder) void { 现在,从理解初始构建脚本到完全扩展,还缺少一个部分: -## 运行已构建的应用程序 +# 运行已构建的应用程序 为了开发用户体验和一般便利性,从构建脚本中直接运行程序是非常实用的。这通常是通过运行步骤实现的,可以通过 zig build run 调用。 @@ -324,11 +324,11 @@ pub fn build(b: *std.build.Builder) void { 这样就可以在 cli 上的 -- 后面传递参数: -``` +```bash zig build run -- -o foo.bin foo.asm ``` -## 结论 +# 结论 本系列的第一章应该能让你完全理解本文开头的构建脚本,并能创建自己的构建脚本。 diff --git a/content/post/2023-12-28-zig-build-explained-part2.smd b/content/post/2023-12-28-zig-build-explained-part2.smd index 29ec328..b9078c4 100644 --- a/content/post/2023-12-28-zig-build-explained-part2.smd +++ b/content/post/2023-12-28-zig-build-explained-part2.smd @@ -9,15 +9,15 @@ > - 原文链接: https://zig.news/xq/zig-build-explained-part-2-1850 > - API 适配到 Zig 0.11.0 版本 -## 注释 +# 注释 从现在起,我将只提供一个最精简的 build.zig,用来说明解决一个问题所需的步骤。如果你想了解如何将所有这些文件粘合到一个构建文件中,请阅读本系列[第一篇文章](2023-12-24-zig-build-explained-part1)。 -## 在命令行上编译 C 代码 +# 在命令行上编译 C 代码 Zig 有两种编译 C 代码的方法,而且这两种很容易混淆。 -### 使用 zig cc +## 使用 zig cc Zig 提供了 LLVM c 编译器 clang。第一种是 zig cc 或 zig c++,它是与 clang 接近 1:1 的前端。由于我们无法直接从 build.zig 访问这些功能(而且我们也不需要!),所以我将在快速的介绍这个主题。 @@ -41,7 +41,7 @@ zig cc -o example.exe -target x86_64-windows-gnu buffer.c main.c 如你所见,只需向 -target 传递目标三元组,就能调用交叉编译。只需确保所有外部库都已准备好进行交叉编译即可! -## 使用 zig build-exe 和其他工具 +# 使用 zig build-exe 和其他工具 使用 Zig 工具链构建 C 项目的另一种方法与构建 Zig 项目的方法相同: @@ -59,7 +59,7 @@ zig build-exe -lc -target x86_64-windows-gnu main.c buffer.c 你会发现,使用这条编译命令,Zig 会自动在输出文件中附加 .exe 扩展名,并生成 .pdb 调试数据库。如果你在此处传递 --name example,输出文件也会有正确的 .exe 扩展名,所以你不必考虑这个问题。 -## 用 build.zig 创建 C 代码 +# 用 build.zig 创建 C 代码 那么,我们如何用 build.zig 来构建上面的两个示例呢? @@ -112,7 +112,7 @@ exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("buffer.c"), .flags = exe.addCSourceFile(.{.file = std.build.LazyPath.relative("buffer.c"), .flags = &.{"-fno-sanitize=undefined"}}); ``` -## 使用外部库 +# 使用外部库 通常情况下,C 项目依赖于其他库,这些库通常预装在 Unix 系统中,或通过软件包管理器提供。 @@ -181,7 +181,7 @@ zig build ./zig-out/bin/downloader https://mq32.de/public/ziggy.txt ``` -## 配置路径 +# 配置路径 由于我们不能在交叉编译项目中使用 pkg-config,或者我们想使用预编译的专用库(如 BASS 音频库),因此我们需要配置包含路径和库路径。 @@ -220,7 +220,7 @@ pub fn build(b: *std.Build) void { addIncludePath 和 addLibraryPath 都可以被多次调用,以向编译器添加多个路径。这些函数不仅会影响 C 代码,还会影响 Zig 代码,因此 @cImport 可以访问包含路径中的所有头文件。 -## 每个文件的包含路径 +# 每个文件的包含路径 因此,如果我们需要为每个 C 文件设置不同的包含路径,我们就需要用不同的方法来解决这个问题: 由于我们仍然可以通过 addCSourceFile 传递任何 C 编译器标志,因此我们也可以在这里手动设置包含目录。 @@ -252,7 +252,7 @@ pub fn build(b: *std.Build) void { 上面的示例非常简单,所以你可能会想为什么需要这样的东西。答案是,有些库的头文件名称非常通用,如 api.h 或 buffer.h,而您希望使用两个共享头文件名称的不同库。 -## 构建 C++ 项目 +# 构建 C++ 项目 到目前为止,我们只介绍了 C 文件,但构建 C++ 项目并不难。你仍然可以使用 addCSourceFile,但只需传递一个具有典型 C++ 文件扩展名的文件,如 cpp、cxx、c++ 或 cc: @@ -285,7 +285,7 @@ pub fn build(b: *std.Build) void { 这就是构建 C++ 文件所需的全部知识,没有什么更神奇的了。 -## 指定语言版本 +# 指定语言版本 试想一下,如果你创建了一个庞大的项目,其中的 C 或 C++ 文件有新有旧,而且可能是用不同的语言标准编写的。为此,我们可以使用编译器标志来传递 -std=c90 或 -std=c++98: @@ -320,7 +320,7 @@ pub fn build(b: *std.Build) void { } ``` -## 条件编译 +# 条件编译 与 Zig 相比,C 和 C++ 的条件编译方式非常繁琐。由于缺乏惰性求值的功能,有时必须根据目标环境来包含/排除文件。你还必须提供宏定义来启用/禁用某些项目功能。 @@ -373,7 +373,7 @@ pub fn build(b: *std.Build) void { 有条件地包含文件就像使用 if 一样简单,你可以这样做。只要不根据你想在构建脚本中定义的任何约束条件调用 addCSourceFile 即可。只包含特定平台的文件?看看上面的脚本就知道了。根据系统时间包含文件?也许这不是个好主意,但还是有可能的! -## 编译大型项目 +# 编译大型项目 由于大多数 C(更糟糕的是 C++)项目都有大量文件(SDL2 有 411 个 C 文件和 40 个 C++ 文件),我们必须找到一种更简单的方法来编译它们。调用 addCSourceFile 400 次并不能很好地扩展。 @@ -478,7 +478,7 @@ pub fn build(b: *std.build.Builder) !void { 注意:其他构建系统会考虑文件名,而 Zig 系统不会!例如,在一个 qmake 项目中不能有两个名为 data.c 的文件!Zig 并不在乎,你可以添加任意多的同名文件,只要确保它们在不同的文件夹中就可以了 😏。 -## 编译 Objective C +# 编译 Objective C 我完全忘了!Zig 不仅支持编译 C 和 C++,还支持通过 clang 编译 Objective C! @@ -514,7 +514,7 @@ pub fn build(b: *std.Build) void { 在这里,链接 libc 是隐式的,因为添加框架会自动强制链接 libc。是不是很酷? -## 混合使用 C 和 Zig 源代码 +# 混合使用 C 和 Zig 源代码 现在,是最后一章: 混合 C 代码和 Zig 代码! @@ -554,7 +554,7 @@ pub fn build(b: *std.Build) void { 您应用程序的入口点现在必须在 Zig 代码中,因为根文件必须导出一个 pub fn main(...) ……。 因此,如果你想将 C 项目中的代码移植到 Zig 中,你必须将 argc 和 argv 转发到你的 C 代码中,并将 C 代码中的 main 重命名为其他函数(例如 oldMain),然后在 Zig 中调用它。如果需要 argc 和 argv,可以通过 std.process.argsAlloc 获取。或者更好: 在 Zig 中重写你的入口点,然后从你的项目中移除一些 C 语言! -## 结论 +# 结论 假设你只编译一个输出文件,那么现在你应该可以将几乎所有的 C/C++ 项目移植到 build.zig。 diff --git a/content/post/2023-12-29-zig-build-explained-part3.smd b/content/post/2023-12-29-zig-build-explained-part3.smd index b69f3e6..64371a8 100644 --- a/content/post/2023-12-29-zig-build-explained-part3.smd +++ b/content/post/2023-12-29-zig-build-explained-part3.smd @@ -11,17 +11,17 @@ 从现在起,我将只提供一个最精简的 build.zig,用来说明解决一个问题所需的步骤。如果你想了解如何将所有这些文件粘合到一个构建文件中,请阅读本系列[第一篇文章](2023-12-24-zig-build-explained-part1)。 -## 复合项目 +# 复合项目 有很多简单的项目只包含一个可执行文件。但是,一旦开始编写库,就必须对其进行测试,通常会编写一个或多个示例应用程序。当人们开始使用外部软件包、C 语言库、生成代码等时,复杂性也会随之上升。 本文试图涵盖所有这些用例,并将解释如何使用 build.zig 来编写多个程序和库。 -## 软件包 +# 软件包 译者:此处代码和说明,需要 zig build-exe --pkg-begin,但是在 0.11 已经失效。所以删除。 -## 库 +# 库 但 Zig 也知道库这个词。但我们不是已经讨论过外部库了吗? @@ -36,7 +36,7 @@ 在 Zig 中,我们需要导入库的头文件,如果头文件在 Zig 中,则使用包,如果是 C 语言头文件,则使用 @cImport。 -## 工具 +# 工具 如果我们的项目越来越多,那么在构建过程中就需要使用工具。这些工具通常会完成以下任务: @@ -48,7 +48,7 @@ 但我们如何在 build.zig 中完成这些工作呢? -## 添加软件包 +# 添加软件包 添加软件包通常使用 LibExeObjStep 上的 addPackage 函数。该函数使用一个 std.build.Pkg 结构来描述软件包的外观: @@ -98,7 +98,7 @@ exe.addModule("lola",pkgs.lola); exe.addModule("args",pkgs.args); ``` -## 添加库 +# 添加库 添加库相对容易,但我们需要配置更多的路径。 @@ -106,7 +106,7 @@ exe.addModule("args",pkgs.args); 假设我们要将 libcurl 链接到我们的项目,因为我们要下载一些文件。 -### 系统库 +## 系统库 对于 unixoid 系统,我们通常可以使用系统软件包管理器来链接系统库。方法是调用 linkSystemLibrary,它会使用 pkg-config 自行找出所有路径: @@ -137,7 +137,7 @@ pub fn build(b: *std.Build) void { 对于 Linux 系统,这是链接外部库的首选方式。 -### 本地库 +## 本地库 不过,您也可以链接您作为二进制文件提供商的库。为此,我们需要调用几个函数。首先,让我们来看看这样一个库是什么样子的: @@ -167,7 +167,7 @@ include 我们可以看到,vendor/libcurl/include 路径包含我们的头文件,vendor/libcurl/lib 文件夹包含一个静态库(libcurl.a)和一个共享/动态库(libcurl.so)。 -### 动态链接 +## 动态链接 要链接 libcurl,我们需要先添加 include 路径,然后向 zig 提供库的前缀和库名:(todo 代码有待验证,因为 curl 可能需要自己编译自己生成 static lib) @@ -197,7 +197,7 @@ addLibraryPath 对库文件也有同样的作用。这意味着 Zig 现在也会 最后,linkSystemLibrary 会告诉 Zig 搜索名为 "curl "的库。如果你留心观察,就会发现上面列表中的文件名是 libcurl.so,而不是 curl.so。在 unixoid 系统中,库文件的前缀通常是 lib,这样就不会将其传递给系统。在 Windows 系统中,库文件的名字应该是 curl.lib 或类似的名字。 -## 静态链接 +# 静态链接 当我们要静态链接一个库时,我们必须采取一些不同的方法: @@ -250,7 +250,7 @@ pub fn build(b: *std.build.Builder) void { 我们可以继续静态链接越来越多的库,并拉入完整的依赖关系树。 -## 通过源代码链接库 +# 通过源代码链接库 不过,我们还有一种与 Zig 工具链截然不同的链接库方式: @@ -293,7 +293,7 @@ pub fn build(b: *std.build.Builder) void { 这一点尤其方便,因为我们可以使用 setTarget 和 setBuildMode 从任何地方编译到任何地方。 -## 使用工具 +# 使用工具 在工作流程中使用工具,通常是在需要以 bison、flex、protobuf 或其他形式进行预编译时。工具的其他用例包括将输出文件转换为不同格式(如固件映像)或捆绑最终应用程序。 @@ -352,7 +352,7 @@ size 是一个很好的工具,它可以输出有关可执行文件代码大小 如您所见,我们在这里使用了 addArtifactArg,因为 addSystemCommand 只会返回一个 std.build.RunStep。这样,我们就可以增量构建完整的命令行,包括任何 LibExeObjStep 输出、FileSource 或逐字参数。 -## 全新工具 +# 全新工具 最酷的是 我们还可以从 LibExeObjStep 获取 std.build.RunStep: @@ -389,7 +389,7 @@ pub fn build(b: *std.build.Builder) void { 调用 zig build pack 时,我们将运行 tools/pack.zig。这很酷,因为我们还可以从头开始编译所需的工具。为了获得最佳的开发体验,你甚至可以从源代码编译像 bison 这样的 "外部 "工具,这样就不会依赖系统了! -## 将所有内容放在一起 +# 将所有内容放在一起 一开始,所有这些都会让人望而生畏,但如果我们看一个更大的 build.zig 实例,就会发现一个好的构建文件结构会给我们带来很大帮助。 @@ -491,7 +491,7 @@ pub fn build(b: *std.build.Builder) void { 两者都是为了在主机平台上运行,而不是在目标机器上。 此外,deploy_tool 还设置了固定的编译模式,因为我们希望快速编译,即使我们编译的是应用程序的调试版本。 -## 总结 +# 总结 看完这一大堆文字,你现在应该可以构建任何你想要的项目了。我们已经学会了如何编译 Zig 应用程序,如何为其添加任何类型的外部库,甚至如何为发布管理对应用程序进行后处理。 diff --git a/content/post/2024-05-07-package-hash.smd b/content/post/2024-05-07-package-hash.smd index d0b4a92..ee902fb 100644 --- a/content/post/2024-05-07-package-hash.smd +++ b/content/post/2024-05-07-package-hash.smd @@ -12,7 +12,7 @@ 作者 Michał Sieroń 最近在思考 `build.zig.zon` 中的依赖项哈希值的问题。这些哈希值都有相同的前缀,而这对加密哈希函数来说极其不同寻常。习惯性使用 Conda 和 Yocto 对下载的压缩包运行 sha256sum,但生成的摘要与 `build.zig.zon` 中的哈希值完全不同。 -```bash +```zon .dependencies = .{ .mach_freetype = .{ .url = "https://pkg.machengine.org/mach-freetype/309be0cf11a2f617d06ee5c5bb1a88d5197d8b46.tar.gz", @@ -42,7 +42,7 @@ 该哈希值是基于一系列文件内容计算得出的,这些文件是在获取URL后并应用了路径规则后得到的。 这个字段是最重要的;一个包是的唯一性是由它的哈希值确定的,不同的 URL 可能对应同一个包。 -## 多重哈希 +# 多重哈希 在他们的网站上有一个很好的可视化展示,说明了这一过程: [多重哈希](https://multiformats.io/multihash/)。 @@ -50,7 +50,7 @@ 因此 `build.zig.zon` 中的哈希字段不仅包含了摘要(digest),还包含了一些元数据(metadata)。但即使我们丢弃了头部信息,得到的结果仍与下载的 `tar` 包的 `sha256` 摘要不相符。而这就涉及到了包含规则的问题。 -## 包含规则(inclusion rules) +# 包含规则(inclusion rules) 回到 [doc/build.zig.zon.md](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/doc/build.zig.zon.md) 文件,我们看到: @@ -91,7 +91,7 @@ pub fn includePath(self: Filter, sub_path: []const u8) bool { 除此之外,这个函数会检查 `sub_path` 是否被明确列出,或者是已明确列出的目录的子目录。 -## 计算哈希 +# 计算哈希 现在我们知道了 `build.zig.zon` 的包含规则,也知道使用了 `SHA256` 算法。但我们仍然不知道实际的哈希结果是如何得到的。例如,它可能是通过将所有包含的文件内容输入哈希器来计算的。所以让我们再仔细看看,也许我们可以找到答案。 @@ -119,7 +119,7 @@ try all_files.append(hashed_file); 跟踪 `workerHashFile`,我们看到它是 `hashFileFallible` 的一个简单包装,而后者看起来相当复杂。让我们来分解一下。 -## 单个文件的哈希计算 +# 单个文件的哈希计算 首先,会进行一些初始化设置,其中创建并用规整后的文件路径初始化了一个新的哈希器实例: @@ -167,7 +167,7 @@ hasher.update(link_name); 首先进行路径分隔符的规整,保证不同平台一致,之后将符号链接的目标路径输入 `hasher`。在 `hashFileFallible` 函数最后,把计算出的哈希值赋值给 `HashedFile` 对象的 `hash` 字段。 -## 组合哈希 +# 组合哈希 尽管有了单个文件的哈希值,但我们仍不知道如何得到最终的哈希。幸运的是,曙光就在眼前。 @@ -197,7 +197,7 @@ for (all_files.items) |hashed_file| { 在这里我们看到所有计算出的哈希被一个接一个地输入到一个新的哈希器中。在 `computeHash` 的最后,我们返回 `hasher.finalResult()`,现在我们明白哈希值是如何获得的了。 -## 最终多哈希值 +# 最终多哈希值 现在我们有了一个 `SHA256` 摘要,可以最终返回到 `main.zig`,在那里我们调用 [`Package.Manifest.hexDigest(fetch.actual_hash)`](https://github.com/ziglang/zig/blob/9d64332a5959b4955fe1a1eac793b48932b4a8a8/src/Package/Manifest.zig#L174)。在那里,我们将多哈希头写入缓冲区,之后是我们的组合摘要。 diff --git a/content/post/2024-08-12-zoop.smd b/content/post/2024-08-12-zoop.smd index 61b18b3..cd4fd46 100644 --- a/content/post/2024-08-12-zoop.smd +++ b/content/post/2024-08-12-zoop.smd @@ -16,7 +16,7 @@ zoop 是 zig 的一个 OOP 解决方案,详细信息可以看看 [zoop官网]( # zoop 入门 -## 类和方法 +# 类和方法 ```zig pub const Base = struct { @@ -73,7 +73,7 @@ pub const Base = struct { 上面的代码给 `Base` 添加了一个可以继承的方法 `getName()`。 -## 类的继承 +# 类的继承 zoop 引入一个关键字 `extends` 用来实现继承,比如下面我们定义 `Base` 的子类 `Child`: @@ -102,7 +102,7 @@ test { } ``` -## 接口定义 +# 接口定义 zoop 中的接口,实际上是一个胖指针。下面我们定义一个接口 `IGetName`: @@ -132,7 +132,7 @@ pub const IGetName = struct { 上面的代码具体原理下面会说到,这里大家知道接口就是这样定义的就行了。上面的代码定义了接口 `IGetName`,这个接口有一个方法 `getName()`。 -## 接口实现 +# 接口实现 上面的 `Base` 类正好也有个符合 `IGetName` 接口的方法 `getName()`,那我们修改一下 `Base` 的代码让它来实现 `IGetName` 接口: @@ -148,7 +148,7 @@ pub const Base = struct { 可以看到实现接口和继承用的同样一个关键字 `extends`。因为子类会继承父类的接口,所以这样一来,`Child` 也自动实现了 `IGetName` 接口。 -## 方法重写和虚函数调用 +# 方法重写和虚函数调用 我们修改上面 `Child` 的代码,重写 `getName()` 方法: @@ -347,11 +347,11 @@ OOP 概念中的继承,重写,虚函数,实质其实就是在编译时动 下面我介绍一下 zoop 中用到的 `comptime` 一些技巧,相信会对大家今后使用 zig 有帮助。 -## `struct` 很万能 +# `struct` 很万能 `comptime` 编程中,`struct` 是你最好的朋友,想在不同的 `comptime` 函数之间传递数据,最方便的方式,就是通过构造一个 `struct`,把想传递的数据通过 `pub const xxx = ...` 的方式传递出去,通过 `struct` 保存数据最好的地方,就在于这个数据在运行时也是可用的 (`struct` 中的常量,是保存在 exe 的 `.data` 区,运行时可见),[zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple) 就是通过这个方法实现的。 -## 动态构造 `struct` 的字段,用 `@Type()` +# 动态构造 `struct` 的字段,用 `@Type()` 网上好像很少有关于 `@Type()` 的使用说明,一般都是通过看 `zig.std` 的代码来学习,那我这里就稍微说明一下,希望能对大家有帮助。 目前 zig 通过 `@Type()`,能动态构造的 `struct`,只有纯字段类型的 `struct` (个人理解)。构造的方法,就是先把计算好的一个 `std.builtin.Type.StructField` 数组传递给 `@Type()` 来返回一个 `struct`,比如以下代码: @@ -398,7 +398,7 @@ const MyStruct = struct { zoop 动态构造 `Vtable` 就是通过这个方法做到的,参考 [zoop.DefVtable 原理](https://zhuyadong.github.io/zoop-docs/reference/principle#DefVtable) 和 [zoop 源代码](https://github.com/zhuyadong/zoop.git) -## 动态构造 `struct` 的函数,用 `usingnamespace` +# 动态构造 `struct` 的函数,用 `usingnamespace` 要想定义 `struct` 中的函数,理论上代码是一定要写在 `struct` 中的,目前 zig 唯一留下的一个口子,就是 `usingnamespace`,zoop 正是利用这个特性,来动态构造 `struct` 的函数。 @@ -442,7 +442,7 @@ pub usingnamespace struct { 因而实现了对 `Base.setName()` 方法的继承。 -## 运行时根据类型找 `Vtable` 和父类指针 +# 运行时根据类型找 `Vtable` 和父类指针 这个功能的实现当时第一版是使用的 `std.StaticStringMap` 保存了一个类中所有接口名到接口 `Vtable` ,以及父类名到父类数据在本类中的地址偏移的映射。和 C++ 的 `dynamic_cast` 比起来,性能是比较差的。后来看到西瓜大大发的一个链接 [点这里](https://github.com/SuperAuguste/cursed-zig-errors),忽然意识到这不就是我一直想要的 `comptime` 全局变量么,我终于能写出 `typeId(comptime T: type) u32` 这样的函数了: diff --git a/content/post/2024-11-26-typed-fsm.smd b/content/post/2024-11-26-typed-fsm.smd index 10325a1..2241309 100644 --- a/content/post/2024-11-26-typed-fsm.smd +++ b/content/post/2024-11-26-typed-fsm.smd @@ -8,7 +8,7 @@ # 1. 简单介绍类型化有限状态机的优势 -## 1.1 介绍有限状态机 +# 1.1 介绍有限状态机 有限状态机(FSM,以下简称状态机)是程序中很常见的设计模式。 @@ -16,7 +16,7 @@ 而状态主要是在代码层面帮助人们理解消息的产生和处理。 -## 1.2 [typed-fsm-zig](https://github.com/sdzx-1/typed-fsm-zig) +# 1.2 [typed-fsm-zig](https://github.com/sdzx-1/typed-fsm-zig) typed-fsm-zig 是一个利用 zig 类型系统加一些编程规范实现的一个库,用于实现类型安全的有限状态机。 @@ -43,7 +43,7 @@ typed-fsm-zig 是一个利用 zig 类型系统加一些编程规范实现的一 为了简单性,这里我不展示构建 ATM 这个例子的过程,感兴趣的可以在这里看到[代码](https://github.com/sdzx-1/typed-fsm-zig/blob/master/examples/atm-gui.zig)。 -## 2.1 介绍 ATM 状态机 +# 2.1 介绍 ATM 状态机 ATM 代表自动取款机,因此它的代码的逻辑就是模拟自动取款机的一些行为:插入银行卡,输入 pin,检查 pin,取钱,修改 pin。 @@ -74,7 +74,7 @@ ATM 代表自动取款机,因此它的代码的逻辑就是模拟自动取款 接下来的文章中我将修改 Update 的行为,并展示在这个过程中类型系统如何帮助我快速调整代码。 -## 2.2 修改 Update 消息 +# 2.2 修改 Update 消息 实际的消息 Update 定义代码如下 @@ -132,7 +132,7 @@ referenced by: 在这里类型系统精确的告诉了我们需要修改的地方,以及原因。修改完成后程序即能正确运行。 -## 2.3 移除 changePin 状态 +# 2.3 移除 changePin 状态 这一节中我们尝试移除 changePin 状态,看看类型系统会给我们什么反馈。 如果移除 changePin,新的状态图如下: @@ -175,7 +175,7 @@ examples/atm-gui.zig:296:10: error: no field named 'ChangePin' in enum '@typeInf 在这个过程中类型系统帮助我们找到问题和原因。这非常酷!!! -## 2.4 总结 +# 2.4 总结 以上是一个简单的例子,展示了 typed-fsm-zig 对于提升状态机编程体验的巨大效果。 diff --git a/content/post/2025-01-23-bonkers-comptime.smd b/content/post/2025-01-23-bonkers-comptime.smd index d156dda..a61fd52 100644 --- a/content/post/2025-01-23-bonkers-comptime.smd +++ b/content/post/2025-01-23-bonkers-comptime.smd @@ -10,7 +10,7 @@ > 译注:原文中的代码块是交互式,翻译时并没有移植。另外,由于 comptime 本身即是关键概念,并且下文的意思更侧重于 Zig comptime 的特性,故下文大多使用 comptime 代替编译时概念。 -## 引子 +# 引子 编程通过自动化地处理数据极大地提升了生产力。而元编程则让我们可以像处理数据一样处理代码,以此将编程的力量反向作用于编程自身。而在底层编程中,我想元编程可能带来最大的优势,因为那些高级概念必须得精确映射到某些低级操作。然而,除了函数式编程语言外,我一直觉得各编程语言对元编程的实现并不理想。因此,当我看到 Zig 把元编程列为一个主要特性时,我提起了很大的兴趣。 @@ -20,7 +20,7 @@ 为了明确起见,所有示例都是有效的 Zig 代码,但示例中的转换只是概念性的,它们并不是 Zig 实际的实现方式。 -## 视角0: 忽略它 +# 视角0: 忽略它 我说我喜欢这个特性,却又立刻叫你忽略它,这确实有点怪。但我认为此处正是 Zig comptime 威力所体现的地方,所以我将从这里出发。Zig Zen 中的第三条是“倾向于阅读代码,而不是编写代码。”确实,能够轻松地阅读代码在各种情况下都很重要,因为它是建立概念理解的基础,而这种理解也是调试或修改代码所必需的。 @@ -71,7 +71,7 @@ pub fn main() void { Zig 中有很多基于 comptime 且远远不止这样简单的类型反射,但你只需要阅读那些代码、完全无需深入了解其中有关 comptime 的细节就可以理解它们在干什么。当然,如果你想使用 comptime 编写代码,则不能仅仅止步于此,让我们继续深入。 -## 视角1: 泛型 +# 视角1: 泛型 泛型在 Zig 中并不是一个特定的功能。相反,Zig 中的仅仅一小部分的 comptime 特性就可以提供用来处理你进行泛型编程所需的一切。这种视角虽然不能让你完全理解 comptime,但它确实为你提供了一个入口点,借此,你可以完成基于元编程的许多任务。 @@ -121,7 +121,7 @@ pub fn main() void { 当然,也可以通过使用特殊类型 anytype 来推断参数的类型,而这通常在参数的类型对函数签名的其余部分没有影响时使用。(译注:此时要限制 a, b, c 的类型相同,所以此处不用 anytype ) -## 视角2:编译时运行的标准代码 +# 视角2:编译时运行的标准代码 这是一个古老的故事: 增加一种自动执行命令的方法。当然,你还需要变量。 哦,还有条件。 拜托,能给我循环吗?这些看似合理的需求,最终导致这些自动化命令变得越来越复杂,甚至演变成一个完整的宏语言。 但 Zig 不同, 在运行时、编译时,甚至是构建系统中都使用了相同的语言。 @@ -178,7 +178,7 @@ pub fn main() !void { comptime 和运行时之间有一些小的区别。比如,只有 comptime 可以访问类型为 comptime_int、comptime_float 或 type 的变量。此外,一些函数只有 comptime 参数,这使它们仅限于编译时环境。相对的,只有运行时才能进行系统调用和那些依赖系统调用的函数。如果你的代码不使用这些特性,那么它在编译时和运行时中的表现将是一样的。 -## 视角3:[程序特化](https://en.wikipedia.org/wiki/Partial_application) +# 视角3:[程序特化](https://en.wikipedia.org/wiki/Partial_application) > 译者注:程序特化(Partial Evaluation)是一种编译优化技术,主要是:在编译期预先计算部分表达式或代码路径,以减少运行时计算开销,提前生成更具体的代码实现。 @@ -255,7 +255,7 @@ onst MyStruct = struct { 上面的示例是我们手动展开后的示例,但这项工作是由 Zig 的 comptime 完成的。这使得我们可以直接独立而完整地编写出我们要实现的功能,而不需要添加"当你改变 `MyStruct` 的字段时,记得更新 sum 函数"这样的由于依赖于 `MyStruct` 具体字段而预防功能失效的注释。 基于 comptime 的版本在 `MyStruct` 的任何字段变更时都可以正确地自动处理。 -## 视角4:Comptime 求值,运行时代码生成 +# 视角4:Comptime 求值,运行时代码生成 这与程序特化(Partial Evaluation)非常相似。这里有两个版本的代码,输入(编译前)和输出(编译后)。输入代码由编译器运行。如果一个语句在编译时是可知的,它就会被直接求值。但是如果一个语句需要某些运行时的值,那么这个语句就会被添加到输出代码中。 @@ -305,7 +305,7 @@ const MyStruct = struct { 这样做的另一个后果是,Zig 代码的静态分析要比大多数静态类型语言复杂得多,因为编译器需要运行很大一部分才能确定所有类型。 因此,在 Zig 工具链跟上之前,代码自动补全等编辑工具并不总是能很好地发挥作用。 -## 视角5:直接生成代码(Textual Code Generation) +# 视角5:直接生成代码(Textual Code Generation) 我在文章开头感叹元编程难度。然而,即使在 Zig 中,它仍然是一个强大的工具,在解决某些问题方面也占有一席之地。如果您熟悉这种元编程方法,对 Zig comptime 提供的功能可能会觉得有些残缺。比如, 怎么在写一段代码在运行时能够生成新代码? @@ -368,13 +368,13 @@ pub fn writeMyStructOfType( 与本节相关的是文本宏,如 C 语言中的文本宏。你可以做的大多数正常事情都可以在 comptime 中完成,尽管它们很少采用类似的形式。 不过,文本宏并不能做所有允许做的事情。 例如,你不能决定不喜欢某个 Zig 关键字,然后让宏代替你自己的关键字。 我认为这是一个正确的决定,尽管对于那些习惯了这种能力的人来说,这是一个艰难的过渡。 此外,Zig 参考了半个世纪以来的程序员在这方面的探索,所以它的选择要理智得多。 -## 结论 +# 结论 在阅读 Zig 代码以理解代码行为时,考虑 comptime 并不是必要的。而当编写 comptime 代码时,我通常会将其视为程序特化(Partial Evaluation)的一种形式。然而,如果你知道如何使用不同的元编程方法解决问题,你很可能有能力将其翻译成 comptime 形式。 元编程中直接生成代码的方法的存在,就是我全力支持 Zig 风格的 comptime 元编程的原因。尽管,直接生成代码是几乎是最强大的,但是,在阅读和调试时忽略 comptime 的特性的元编程方法确是最简单的。正因如此,我给本文取名为《Zig comptime 棒极了》。 -## 进一步阅读 +# 进一步阅读 Zig 并非一个仅仅依赖 comptime 这一特性的语言。你可以在[官方网站](https://ziglang.org/)上了解更多关于 Zig 的信息。 diff --git a/content/post/news/2023-12-27-second-meetup.smd b/content/post/news/2023-12-27-second-meetup.smd index e3753d0..71cf0dd 100644 --- a/content/post/news/2023-12-27-second-meetup.smd +++ b/content/post/news/2023-12-27-second-meetup.smd @@ -20,31 +20,31 @@ action,主要是同步了不同项目的进展,由于临近年底,大家 # 项目进展 -## [Zig-OS](https://github.com/zigcc/zig-os) +# [Zig-OS](https://github.com/zigcc/zig-os) - 主要参与人员:西瓜 - 进展:粗略看完 rust 版本的教程;完成 freestanding 二进制,现在卡在了 bootloader 阶段 -## [Learn zig](https://github.com/learnzig/learnzig) +# [Learn zig](https://github.com/learnzig/learnzig) - 主要参与人员:金中甲 - zig的进阶特性,诸如构建系统、包管理、与C交互均已完成,目前教程内容已基本覆盖日常使用 - 增加了评论区的功能 - 待完成:反射(编译期反射和运行时反射)、内建函数说明(包含使用例子)、未定义行为、wasm、原子操作这些边缘部分 -## Zig 教学视频 +# Zig 教学视频 - 主要参与人员:Lambert - - 暂无明显进展 -## [Zig cookbook](https://github.com/zigcc/zig-cookbook) +# [Zig cookbook](https://github.com/zigcc/zig-cookbook) - 主要参与人员:夜白、西瓜 - 已经完成大部分内容 👍 -## Zig 构建系统教程 +# Zig 构建系统教程 - 主要参与人员:Reco - 目前主要是对 [zig build From 26ca0a874ab7a8cc4fb39c7c1cb07d7506345099 Mon Sep 17 00:00:00 2001 From: xihale Date: Wed, 2 Jul 2025 08:30:42 +0800 Subject: [PATCH 13/22] Remove obsolete JavaScript file and standardize markdown headings across various SMD files for improved content structure. Update section titles to use consistent formatting, enhancing readability and organization. --- content/about.smd | 4 +- content/community.smd | 2 +- content/contributing.smd | 6 +- content/learn/02-installing-zig.smd | 2 +- content/learn/03-language-overview-1.smd | 14 +- content/learn/04-language-overview-2.smd | 12 +- content/learn/05-style-guide.smd | 6 +- content/learn/06-pointers.smd | 10 +- content/learn/07-stack-memory.smd | 4 +- content/learn/08-heap-memory.smd | 18 +- content/learn/10-coding-in-zig.smd | 14 +- content/learn/index.smd | 10 +- content/monthly/202207.smd | 6 +- content/monthly/202208.smd | 6 +- content/monthly/202209.smd | 12 +- content/monthly/202210.smd | 6 +- content/monthly/202211.smd | 10 +- content/monthly/202212.smd | 6 +- content/monthly/202301.smd | 8 +- content/monthly/202302.smd | 8 +- content/monthly/202303.smd | 8 +- content/monthly/202304.smd | 8 +- content/monthly/202305.smd | 8 +- content/monthly/202306.smd | 10 +- content/monthly/202307.smd | 8 +- content/monthly/202308.smd | 32 +-- content/monthly/202309.smd | 12 +- content/monthly/202310.smd | 8 +- content/monthly/202311.smd | 8 +- content/monthly/202402.smd | 8 +- content/monthly/202403.smd | 8 +- content/monthly/202404.smd | 8 +- content/monthly/202405.smd | 14 +- content/monthly/202406.smd | 19 +- content/monthly/202407.smd | 16 +- content/monthly/202410.smd | 34 ++-- content/monthly/202411.smd | 14 +- content/post/0.14.smd | 34 ++-- content/post/2023-09-05-bog-gc-1-en.smd | 16 +- content/post/2023-09-05-bog-gc-1.smd | 42 ++-- content/post/2023-09-05-hello-world.smd | 4 +- content/post/2023-09-21-zig-midi.smd | 38 ++-- .../2023-12-24-zig-build-explained-part1.smd | 16 +- .../2023-12-28-zig-build-explained-part2.smd | 34 ++-- .../2023-12-29-zig-build-explained-part3.smd | 30 +-- ...2-how-to-release-your-zig-applications.smd | 188 +++++++++--------- content/post/2024-04-06-zig-cpp.smd | 24 +-- content/post/2024-05-07-package-hash.smd | 24 ++- content/post/2024-05-24-interface-idioms.smd | 14 +- content/post/2024-06-10-zig-hashmap-1.smd | 8 +- content/post/2024-06-11-zig-hashmap-2.smd | 8 +- content/post/2024-08-12-zoop.smd | 30 +-- content/post/2024-11-26-typed-fsm.smd | 24 ++- content/post/2025-01-23-bonkers-comptime.smd | 18 +- content/post/news/2023-12-11-first-meetup.smd | 2 +- .../post/news/2023-12-27-second-meetup.smd | 14 +- content/post/news/2024-01-14-third-meetup.smd | 6 +- fix_zig_update_section.js | 43 ---- 58 files changed, 477 insertions(+), 527 deletions(-) delete mode 100644 fix_zig_update_section.js diff --git a/content/about.smd b/content/about.smd index 3aaed07..3642810 100644 --- a/content/about.smd +++ b/content/about.smd @@ -6,7 +6,7 @@ .draft = false, --- -# About Zine +## About Zine Zine is an MIT-licensed project created by [Loris Cro](https://kristoff.it) and other contributors listed on the [official repository](https://github.com/kristoff-it/zine/contributors). @@ -34,7 +34,7 @@ of authoring languages: ># [NOTE]($block) >The correct file extension for SuperMD pages is `.smd`. -# Zine is alpha software +## Zine is alpha software Zine is not yet complete. The main functionality is present and you will be able to build even moderately complex static websites without issue. diff --git a/content/community.smd b/content/community.smd index debd6a2..9ce6ae6 100644 --- a/content/community.smd +++ b/content/community.smd @@ -8,7 +8,7 @@ TODO: issue [让我们一起探索 Zig 的魅力,推动 Zig 在中文社区内的发展!]($text.attrs('center','large','bold')) -# 网站更新日志 +## 网站更新日志 - **2025-06-30:** 切换到 [zine](https://zine-ssg.io/) - **2024-08-18:** 切换主题 [docsy](https://github.com/google/docsy) diff --git a/content/contributing.smd b/content/contributing.smd index 0858c2a..b0fec1e 100644 --- a/content/contributing.smd +++ b/content/contributing.smd @@ -12,7 +12,7 @@ Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文 2. 改进 zigcc 组织下的开源项目,这是 [open issues](https://github.com/search?q=state%3Aopen+org%3Azigcc++NOT+%E6%97%A5%E6%8A%A5&type=issues&ref=advsearch) 3. 参与不定期的线上会议 TODO -# 供稿方式 +## 供稿方式 1. Fork 仓库 https://github.com/zigcc/zigcc.github.io 2. 在 `content/post` 内添加自己的文章(md 或 org 格式均可),文件命名为: `${YYYY}-${MM}-${DD}-${SLUG}.md` @@ -26,13 +26,13 @@ date: '2023-09-05T16:13:13+0800' --- ``` -# 本地预览 +## 本地预览 TODO ```bash zine ``` -# 发布平台 +## 发布平台 - [ZigCC 网站](https://ziglang.cc) - [ZigCC 公众号](https://github.com/zigcc/.github/raw/main/zig_mp.png) diff --git a/content/learn/02-installing-zig.smd b/content/learn/02-installing-zig.smd index 35892b6..50879fc 100644 --- a/content/learn/02-installing-zig.smd +++ b/content/learn/02-installing-zig.smd @@ -25,7 +25,7 @@ EOF asdf plugin-add zig https://github.com/zigcc/asdf-zig.git -# 安装最新版 +## 安装最新版 asdf install zig latest asdf global zig latest zig version diff --git a/content/learn/03-language-overview-1.smd b/content/learn/03-language-overview-1.smd index 0820688..ce227bd 100644 --- a/content/learn/03-language-overview-1.smd +++ b/content/learn/03-language-overview-1.smd @@ -37,7 +37,7 @@ pub const User = struct { > 请参阅[安装 Zig 部分](02-installing-zig),以便快速启动并运行它。 -# [模块引用]($section.id('module-reference')) +## [模块引用]($section.id('module-reference')) 很少有程序是在没有标准库或外部库的情况下以单个文件编写的。我们的第一个程序也不例外,它使用 Zig 的标准库来进行打印输出。 Zig 的模块系统非常简单,只依赖于 `@import` 函数和 `pub` 关键字(使代码可以在当前文件外部访问)。 @@ -88,7 +88,7 @@ const MAX_POWER = user.MAX_POWER; - 如何导入其他文件 - 如何导出变量、函数定义 -# [代码注释]($section.id('code-comment')) +## [代码注释]($section.id('code-comment')) 下面这行 Zig 代码是一个注释: @@ -100,7 +100,7 @@ Zig 没有像 C 语言中类似 `/* ... */` 的多行注释。 基于注释的文档自动生成功能正在试验中。如果你看过 Zig 的标准库文档,你就会看到它的实际应用。`//!` 被称为顶级文档注释,可以放在文件的顶部。三斜线注释 (`///`) 被称为文档注释,可以放在特定位置,如声明之前。如果在错误的地方使用这两种文档注释,编译器都会出错。 -# [函数]($section.id('function')) +## [函数]($section.id('function')) 下面这行 Zig 代码是程序的入口函数 `main`: @@ -146,7 +146,7 @@ fn add(a: i64, b: i64) i64 { 为了提高可读性,Zig 中不支持函数重载(用不同的参数类型或参数个数定义的同名函数)。暂时来说,以上就是我们需要了解的有关函数的全部内容。 -# [结构体]($section.id('struct')) +## [结构体]($section.id('struct')) 下面这行代码创建了一个 `User` 结构体: @@ -266,7 +266,7 @@ pub fn init(name: []const u8, power: u64) User { 就像我们迄今为止已经探索过的大多数东西一样,今后在讨论 Zig 语言的其他部分时,我们会再次讨论结构体。不过,在大多数情况下,它们都是简单明了的。 -# [数组和切片]($section.id('array-slice')) +## [数组和切片]($section.id('array-slice')) 我们可以略过代码的最后一行,但鉴于我们的代码片段包含两个字符串 `"Goku"` 和 `{s}'s power is {d}\n`,你可能会对 Zig 中的字符串感到好奇。为了更好地理解字符串,我们先来了解一下数组和切片。 @@ -365,7 +365,7 @@ pub fn main() void { 在了解 Zig 语言的其他方面(尤其是字符串)的同时,我们还将发现更多有关数组和切片的知识。 -# [字符串]($section.id('string')) +## [字符串]($section.id('string')) 我希望我能说,Zig 里有字符串类型,而且非常棒。遗憾的是,它没有。最简单来说,字符串是字节(u8)的序列(即数组或切片)。实际上,我们可以从 `name` 字段的定义中看到这一点:`name: []const u8`. @@ -399,7 +399,7 @@ pub fn main() void { 当然,在实际程序中,大多数字符串(以及更通用的数组)在编译时都是未知的。最典型的例子就是用户输入,程序编译时并不知道用户输入。这一点我们将在讨论内存时再次讨论。但简而言之,对于这种在编译时不能确定值的数据(长度当然也就无从得知),我们将在运行时动态分配内存。我们的字符串变量(仍然是 `[]const u8` 类型)将是指向动态分配的内存的切片。 -# [comptime 和 anytype]($section.id('comptime-anytype')) +## [comptime 和 anytype]($section.id('comptime-anytype')) 在我们未解释的最后一行代码中,涉及的知识远比表面看到的多: diff --git a/content/learn/04-language-overview-2.smd b/content/learn/04-language-overview-2.smd index 47d586c..c294bec 100644 --- a/content/learn/04-language-overview-2.smd +++ b/content/learn/04-language-overview-2.smd @@ -10,7 +10,7 @@ 本部分继续上一部分的内容:熟悉 Zig 语言。我们将探究 Zig 的控制流和结构以外的类型。通过这两部分的学习,我们将掌握 Zig 语言的大部分语法,这让我们可以继续深入 Zig 语言,同时也为如何使用 std 标准库打下了基础。 -# [控制流]($section.id('kongzhi-liu')) +## [控制流]($section.id('kongzhi-liu')) Zig 的控制流很可能是我们所熟悉的,但它与 Zig 语言的其他特性协同工作是我们还没有探索过。我们先简单概述控制流的基本使用,之后在讨论依赖控制流的相关特性时,再来重新回顾。 @@ -207,7 +207,7 @@ const personality_analysis = blk: { 稍后,当我们讨论带标签的联合(tagged union)、错误联合(error unions)和可选类型(Optional)时,我们将看到控制流如何与它们联合使用。 -# [枚举]($section.id('meiju')) +## [枚举]($section.id('meiju')) 枚举是带有标签的整数常量。它们的定义很像结构体: @@ -241,7 +241,7 @@ const Stage = enum { `switch` 的穷举性质使它能与枚举很好地搭配,因为它能确保你处理了所有可能的情况。不过在使用 `switch` 的 `else` 子句时要小心,因为它会匹配任何新添加的枚举值,而这可能不是我们想要的行为。 -# [带标签的联合 Tagged Union]($section.id('tagged-union')) +## [带标签的联合 Tagged Union]($section.id('tagged-union')) 联合定义了一个值可以具有的一系列类型。例如,这个 `Number` 可以是整数、浮点数或 nan(非数字): @@ -320,7 +320,7 @@ const Timestamp = union(enum) { 这里 Zig 会根据带标签的联合,自动创建一个隐式枚举。 -# [可选类型 Optional]($section.id('optional')) +## [可选类型 Optional]($section.id('optional')) 在类型前加上问号 `?`,任何值都可以声明为可选类型。可选类型既可以是 `null`,也可以是已定义类型的值: @@ -366,7 +366,7 @@ while (rows.next()) |row| { } ``` -# [未定义的值 Undefined]($section.id('undefined')) +## [未定义的值 Undefined]($section.id('undefined')) 到目前为止,我们看到的每一个变量都被初始化为一个合理的值。但有时我们在声明变量时并不知道它的值。可选类型是一种选择,但并不总是合理的。在这种情况下,我们可以将变量设置为未定义,让其保持未初始化状态。 @@ -379,7 +379,7 @@ std.crypto.random.bytes(&pseudo_uuid); 上述代码仍然创建了一个 16 字节的数组,但它的每个元素都没有被赋值。 -# [错误 Errors]($section.id('errors')) +## [错误 Errors]($section.id('errors')) Zig 中错误处理功能十分简单、实用。这一切都从错误集(error sets)开始,错误集的使用方式类似于枚举: diff --git a/content/learn/05-style-guide.smd b/content/learn/05-style-guide.smd index f5e9636..3a491de 100644 --- a/content/learn/05-style-guide.smd +++ b/content/learn/05-style-guide.smd @@ -10,7 +10,7 @@ 本小节的主要内容是介绍 Zig 编译器强制遵守的 2 条规则,以及 Zig 标准库的命名惯例(naming convention)。 -# [未使用变量 Unused Variable]($section.id('unused-variable')) +## [未使用变量 Unused Variable]($section.id('unused-variable')) Zig 编译器禁止`未使用变量`,例如以下代码会导致两处编译错误: @@ -55,7 +55,7 @@ fn add(a: i64, _: i64) i64 { 值得注意的是,在上述例子中,`std`也是一个未使用的符号,但是当前这种用法并不会导致任何编译错误。可能在未来,Zig 编译器也将此视为错误。 -# [变量覆盖 Variable Shadowing]($section.id('variable-shadowing')) +## [变量覆盖 Variable Shadowing]($section.id('variable-shadowing')) Zig 不允许使用同名的变量。下面是一个读取 `socket` 的例子,这个例子包含了一个变量覆盖的编译错误: @@ -74,7 +74,7 @@ fn read(stream: std.net.Stream) ![]const u8 { 我认为,这个规范并不能使代码可读性提高。在这个场景下,应该是开发者,而不是编译器,更有资格选择更有可读性的命名方案。 -# [命名规范]($section.id('naming-convention')) +## [命名规范]($section.id('naming-convention')) 除了遵守以上这些规则以外,开发者可以自由地选择他们喜欢的命名规范。但是,理解 Zig 自身的命名规范是有益的,因为大部分你需要打交道的代码,如 Zig 标准库,或者其他三方库,都采用了 Zig 的命名规范。 diff --git a/content/learn/06-pointers.smd b/content/learn/06-pointers.smd index 241083f..da7ef9c 100644 --- a/content/learn/06-pointers.smd +++ b/content/learn/06-pointers.smd @@ -191,7 +191,7 @@ pub const User = struct { 现在,代码已按预期运行。虽然在函数参数和内存模型方面仍有许多微妙之处,但我们正在取得进展。现在也许是一个好时机来说明一下,除了特定的语法之外,这些都不是 Zig 所独有的。我们在这里探索的模型是最常见的,有些语言可能只是向开发者隐藏了很多细节,因此也就隐藏了灵活性。 -# [方法]($section.id('fangfa')) +## [方法]($section.id('fangfa')) 一般来说,我们会把 `levelUp` 写成 `User`结构的一个方法: @@ -210,7 +210,7 @@ pub const User = struct { 我最初选择函数是因为它很明确,因此更容易学习。 -# [常量函数参数]($section.id('const-func-param')) +## [常量函数参数]($section.id('const-func-param')) 我不止一次暗示过,在默认情况下,Zig 会传递一个值的副本(称为 "按值传递")。很快我们就会发现,实际情况要更微妙一些(提示:嵌套对象的复杂值怎么办?) @@ -222,7 +222,7 @@ pub const User = struct { > 也许你会想,即使与复制一个非常小的结构相比,通过引用传递怎么会更慢呢?我们接下来会更清楚地看到这一点,但要点是,当 `user` 是指针时,执行 `user.power` 会增加一点点开销。编译器必须权衡复制的代价和通过指针间接访问字段的代价。 -# [指向指针的指针]($section.id('pointer-to-pointer')) +## [指向指针的指针]($section.id('pointer-to-pointer')) 我们之前查看了`main`函数中 `user` 的内存结构。现在我们改变了 `levelUp`,那么它的内存会是什么样的呢? @@ -258,7 +258,7 @@ fn levelUp(user: *User) void { 我们可以使用多级间接指针,但这并不是我们现在所需要的。本节的目的是说明指针并不特殊,它只是一个值,即一个地址和一种类型。 -# [嵌套指针]($section.id('nested-pointer')) +## [嵌套指针]($section.id('nested-pointer')) 到目前为止,`User` 一直很简单,只包含两个整数。很容易就能想象出它的内存,而且当我们谈论『复制』 时,也不会有任何歧义。但是,如果 User 变得更加复杂并包含一个指针,会发生什么情况呢? @@ -371,7 +371,7 @@ pub const User = struct { 不同编程语言有不同的实现方式,但许多语言的工作方式与此完全相同(或非常接近)。虽然所有这些看似深奥,但却是日常编程的基础。好消息是,你可以通过简单的示例和片段来掌握这一点;它不会随着系统其他部分复杂性的增加而变得更加复杂。 -# [递归结构]($section.id('recursive-struct')) +## [递归结构]($section.id('recursive-struct')) 有时你需要一个递归结构。在保留现有代码的基础上,我们为 `User` 添加一个可选的 `manager` 字段,类型为 `?User`。同时,我们将创建两个`User`,并将其中一个指定为另一个的管理者: diff --git a/content/learn/07-stack-memory.smd b/content/learn/07-stack-memory.smd index a6df8f7..54e43dc 100644 --- a/content/learn/07-stack-memory.smd +++ b/content/learn/07-stack-memory.smd @@ -18,7 +18,7 @@ > 三块内存区域实际上没有真正的物理差别。操作系统和可执行文件创造了"内存区域"这个概念。 -# 栈帧 +## 栈帧 迄今为止,我们所见的所有数据都是常量,存储在二进制的全局数据部分或作为局部变量。局部表示该变量只在其声明的范围内有效。在 Zig 中,范围从花括号开始到结束。大多数变量的范围限定在一个函数内,包括函数参数,或一个控制流块,比如 if。但是,正如所见,你可以创建任意块,从而创建任意范围。 @@ -92,7 +92,7 @@ main: user -> ------------- (id: 1043368d0) 与全局数据一样,调用栈也由操作系统和可执行文件管理。程序启动时,以及此后启动的每个线程,都会创建一个调用栈(其大小通常可在操作系统中配置)。调用栈在程序的整个生命周期中都存在,如果是线程,则在线程的整个生命周期中都存在。程序或线程退出时,调用栈将被释放。我们的全局数据包含所有程序的全局数据,而调用栈只包含当前执行的函数层次的栈帧。这样做既能有效利用内存,又能简化堆栈帧的管理。 -# 悬空指针 +## 悬空指针 栈帧的简洁和高效令人惊叹。但它也很危险:当函数返回时,它的任何本地数据都将无法访问。这听起来似乎很合理,毕竟这是本地数据,但却会带来严重的问题。请看这段代码: diff --git a/content/learn/08-heap-memory.smd b/content/learn/08-heap-memory.smd index 8b7b879..05ea3f0 100644 --- a/content/learn/08-heap-memory.smd +++ b/content/learn/08-heap-memory.smd @@ -14,7 +14,7 @@ 本部分分为两个主题。第一个主题是第三个内存区域--堆的总体概述。另一个主题是 Zig 直接而独特的堆内存管理方法。即使你熟悉堆内存,比如使用过 C 语言的 `malloc`,你也会希望阅读第一部分,因为它是 Zig 特有的。 -# [堆]($section.id('dui')) +## [堆]($section.id('dui')) 堆是我们可以使用的第三个也是最后一个内存区域。与全局数据和调用栈相比,堆有点像蛮荒之地:什么都可以使用。具体来说,在堆中,我们可以在运行时创建大小已知的内存,并完全控制其生命周期。 @@ -52,7 +52,7 @@ fn getRandomCount() !u8 { 一般来说,每次 `alloc` 都会有相应的 `free`。`alloc`分配内存,`free`释放内存。不要让这段简单的代码限制了你的想象力。这种 `try alloc` + `defer free` 的模式很常见,这是有原因的:在我们分配内存的地方附近释放相对来说是万无一失的。但同样常见的是在一个地方分配,而在另一个地方释放。正如我们之前所说,堆没有内置的生命周期管理。你可以在 HTTP 处理程序中分配内存,然后在后台线程中释放,这是代码中两个完全独立的部分。 -# defer 和 errdefer +## defer 和 errdefer 说句题外话,上面的代码介绍了一个新的语言特性:`defer`,它在退出作用域时执行给定的代码。『作用域退出』包括到达作用域的结尾或从作用域返回。严格来说, `defer` 与分配器或内存管理并无严格关系;你可以用它来执行任何代码。但上述用法很常见。 @@ -100,7 +100,7 @@ pub const Game = struct { > `init` 和 `deinit` 的名字并不特殊。它们只是 Zig 标准库使用的,也是社区采纳的名称。在某些情况下,包括在标准库中,会使用 `open` 和 `close`,或其他更适当的名称。 -# 双重释放和内存泄漏 +## 双重释放和内存泄漏 上面提到过,没有规则规定什么时候必须释放什么东西。但事实并非如此,还是有一些重要规则,只是它们不是强制的,需要你自己格外小心。 @@ -159,7 +159,7 @@ fn isSpecial(allocator: Allocator, name: [] const u8) !bool { 至少在双重释放的情况下,我们的程序会遭遇严重崩溃。内存泄漏可能很隐蔽。不仅仅是根本原因难以确定。真正的小泄漏或不常执行的代码中的泄漏甚至很难被发现。这是一个很常见的问题,Zig 提供了帮助,我们将在讨论分配器时看到。 -# 创建与销毁 +## 创建与销毁 `std.mem.Allocator`的`alloc`方法会返回一个切片,其长度为传递的第二个参数。如果想要单个值,可以使用 `create` 和 `destroy` 而不是 `alloc` 和 `free`。 @@ -233,7 +233,7 @@ fn init(allocator: std.mem.Allocator, id: u64, power: i32) !*User{ 请记住,`create` 返回一个 `!*User`,所以我们的 `user` 是 `*User` 类型。 -# 分配器 Allocator +## 分配器 Allocator Zig 的核心原则之一是无隐藏内存分配。根据你的背景,这听起来可能并不特别。但这与 C 语言中使用标准库的 malloc 函数分配内存的做法形成了鲜明的对比。在 C 语言中,如果你想知道一个函数是否分配内存,你需要阅读源代码并查找对 malloc 的调用。 @@ -254,7 +254,7 @@ defer allocator.free(say); 如果你正在构建一个库,那么最好接受一个 `std.mem.Allocator`,然后让库的用户决定使用哪种分配器实现。否则,你就需要选择正确的分配器,正如我们将看到的,这些分配器并不相互排斥。在你的程序中创建不同的分配器可能有很好的理由。 -# 通用分配器 GeneralPurposeAllocator +## 通用分配器 GeneralPurposeAllocator 顾名思义,`std.heap.GeneralPurposeAllocator` 是一种通用的、线程安全的分配器,可以作为应用程序的主分配器。对于许多程序来说,这是唯一需要的分配器。程序启动时,会创建一个分配器并传递给需要它的函数。我的 HTTP 服务器库中的示例代码就是一个很好的例子: @@ -299,7 +299,7 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 类型是什么,字段在哪里?类型其实是 `std.heap.general_purpose_allocator.Config`,但它并没有直接暴露出来,这也是我们没有显式给出类型的原因之一。没有设置字段是因为 Config 结构定义了默认值,我们将使用默认值。这是配置、选项的中常见的模式。事实上,我们在下面几行向 `init` 传递 `.{.port = 5882}` 时又看到了这种情况。在本例中,除了端口这一个字段外,我们都使用了默认值。 -# std.testing.allocator +## std.testing.allocator 希望当我们谈到内存泄漏时,你已经足够烦恼,而当我提到 Zig 可以提供帮助时,你肯定渴望了解更多这方面内容。这种帮助来自 `std.testing.allocator`,它是一个 `std.mem.Allocator` 实现。目前,它基于通用分配器(GeneralPurposeAllocator)实现,并与 Zig 的测试运行器进行了集成,但这只是实现细节。重要的是,如果我们在测试中使用 `std.testing.allocator`,就能捕捉到大部分内存泄漏。 @@ -423,7 +423,7 @@ self.allocator.free(self.items); 将`items`复制到我们的 `larger` 切片中后, 添加最后一行`free`可以解决泄漏的问题。如果运行 `zig test learning.zig`,便不会再有错误。 -# ArenaAllocator +## ArenaAllocator 通用分配器(GeneralPurposeAllocator)是一个合理的默认设置,因为它在所有可能的情况下都能很好地工作。但在程序中,你可能会遇到一些固定场景,使用更专业的分配器可能会更合适。其中一个例子就是需要在处理完成后丢弃的短期状态。解析器(Parser)通常就有这样的需求。一个 `parse` 函数的基本轮廓可能是这样的 @@ -508,7 +508,7 @@ defer list.deinit(); 最后举个简单的例子,我上面提到的 HTTP 服务器在响应中暴露了一个 `ArenaAllocator`。一旦发送了响应,它就会被清空。由于`ArenaAllocator`的生命周期可以预测(从请求开始到请求结束),因此它是一种高效的选择。就性能和易用性而言,它都是高效的。 -# 固定缓冲区分配器 FixedBufferAllocator +## 固定缓冲区分配器 FixedBufferAllocator 我们要讨论的最后一个分配器是 `std.heap.FixedBufferAllocator`,它可以从我们提供的缓冲区(即 `[]u8`)中分配内存。这种分配器有两大好处。首先,由于所有可能使用的内存都是预先创建的,因此速度很快。其次,它自然而然地限制了可分配内存的数量。这一硬性限制也可以看作是一个缺点。另一个缺点是,`free` 和 `destroy` 只对最后分配/创建的项目有效(想想堆栈)。调用释放非最后分配的内存是安全的,但不会有任何作用。 diff --git a/content/learn/10-coding-in-zig.smd b/content/learn/10-coding-in-zig.smd index c209a02..5a6530f 100644 --- a/content/learn/10-coding-in-zig.smd +++ b/content/learn/10-coding-in-zig.smd @@ -10,7 +10,7 @@ 在介绍了 Zig 语言的大部分内容之后,我们将对一些主题进行回顾,并展示几种使用 Zig 编程时一些实用的技巧。在此过程中,我们将介绍更多的标准库,并介绍一些稍复杂些的代码片段。 -# [悬空指针 Dangling Pointers]($section.id('dangling-pointers')) +## [悬空指针 Dangling Pointers]($section.id('dangling-pointers')) 我们首先来看看更多关于悬空指针的例子。这似乎是一个奇怪的问题,但如果你之前主要使用带垃圾回收的语言,这可能是你学习 Zig 最大的障碍。 @@ -105,7 +105,7 @@ const User = struct { 如果把所有东西都放在一个函数中,再加上一个像 `User` 这样的小值,这仍然像是一个人为制造的问题。我们需要一个能让数据所有权成为当务之急的例子。 -# [所有权 Ownership]($section.id('ownership')) +## [所有权 Ownership]($section.id('ownership')) 我喜欢哈希表(HashMap),因为这是每个人都知道并且会经常使用的结构。它们有很多不同的用例,其中大部分你可能都用过。虽然哈希表可以用在一个短期查找的地方,但通常用于长期查找,因此插入其内的值需要同样长的生命周期。 @@ -218,7 +218,7 @@ defer { 我保证,关于悬挂指针和内存管理的讨论已经结束了。我们所讨论的内容可能还不够清晰或过于抽象。当你有更实际的问题需要解决时,再重新讨论这个问题也不迟。不过,如果你打算编写任何稍具规模(non-trivial)的程序,这几乎肯定是你需要掌握的内容。当你觉得可以的时候,我建议你参考上面这个示例,并自己动手实践一下。引入一个 `UserLookup` 类型来封装我们必须做的所有内存管理。尝试使用 `*User` 代替 `User`,在堆上创建用户,然后像处理键那样释放它们。编写覆盖新结构的测试,使用 `std.testing.allocator` 确保不会泄漏任何内存。 -# [ArrayList]($section.id('arraylist')) +## [ArrayList]($section.id('arraylist')) 现在你可以忘掉我们的 `IntList` 和我们创建的通用替代方案了。Zig 标准库中有一个动态数组实现:`std.ArrayList(T)`。 @@ -313,7 +313,7 @@ pub fn main() !void { } ``` -# [Anytype]($section.id('anytype')) +## [Anytype]($section.id('anytype')) 在[语言概述的第一部分](03-language-overview-1)中,我们简要介绍了 `anytype`。这是一种非常有用的编译时 duck 类型。下面是一个简单的 logger: @@ -383,7 +383,7 @@ fn stringify( 第一个参数 `value: anytype` 是显而易见的,它是要序列化的值,可以是任何类型(实际上,Zig 的 JSON 序列化器不能序列化某些类型,比如 HashMap)。我们可以猜测,`out_stream` 是写入 JSON 的地方,但至于它需要实现什么方法,你和我一样猜得到。唯一的办法就是阅读源代码,或者传递一个假值,然后使用编译器错误作为我们的文档。如果有更好的自动文档生成器,这一点可能会得到改善。不过,我希望 Zig 能提供接口,这已经不是第一次了。 -# [@TypeOf]($section.id('typeof')) +## [@TypeOf]($section.id('typeof')) 在前面的部分中,我们使用 `@TypeOf` 来帮助我们检查各种变量的类型。从我们的用法来看,你可能会认为它返回的是字符串类型的名称。然而,鉴于它是一个 PascalCase 风格函数,你应该更清楚:它返回的是一个 `type`。 @@ -412,7 +412,7 @@ pub const User = struct { 更常见的是 `@TypeOf` 与 `@typeInfo` 配对,后者返回一个 `std.builtin.Type`。这是一个功能强大的带标签的联合(tagged union),可以完整描述一个类型。`std.json.stringify` 函数会递归地调用它,以确定如何将提供的 `value` 序列化。 -# [构建系统]($section.id('build-system')) +## [构建系统]($section.id('build-system')) 如果你通读了整本指南,等待着深入了解如何建立更复杂的项目,包括多个依赖关系和各种目标,那你就要失望了。Zig 拥有强大的构建系统,以至于越来越多的非 Zig 项目都在使用它,比如 libsodium。不幸的是,所有这些强大的功能都意味着,对于简单的需求来说,它并不是最容易使用或理解的。 @@ -497,7 +497,7 @@ test "dummy build test" { 这是启动和运行构建系统所需的最低配置。但是请放心,如果你需要构建你的程序,Zig 内置的功能大概率能覆盖你的需求。最后,你可以(也应该)在你的项目根目录下使用 `zig init`,让 Zig 为你创建一个文档齐全的 `build.zig` 文件。 -# [第三方依赖]($section.id('third-party-deps')) +## [第三方依赖]($section.id('third-party-deps')) Zig 的内置软件包管理器相对较新,因此存在一些缺陷。虽然还有改进的余地,但它目前还是可用的。我们需要了解两个部分:创建软件包和使用软件包。我们将对其进行全面介绍。 diff --git a/content/learn/index.smd b/content/learn/index.smd index a8c39fa..e73fe53 100644 --- a/content/learn/index.smd +++ b/content/learn/index.smd @@ -6,7 +6,7 @@ .draft = false, --- -# [《学习 Zig》 目录]($section.id('table-of-contents')) +## [《学习 Zig》 目录]($section.id('table-of-contents')) - [前言](./01-preface) - [安装 Zig](./02-installing-zig) @@ -24,7 +24,7 @@ 初次接触 Zig 的用户可以按序号依次阅读,对于有经验的 Zig 开发者可按需阅读感兴趣的章节。 -# 关于原作者 +## 关于原作者 [Karl Seguin](https://www.linkedin.com/in/karlseguin/) 在多个领域有着丰富经验,前微软 MVP,他撰写了大量文章,是多个微软公共新闻组的活跃成员。现居新加坡。他还是以下教程的作者: @@ -34,13 +34,13 @@ 可以在 找到他的博客,或者通过 [@karlseguin](http://twitter.com/karlseguin) 在 Twitter 上关注他。 -# 翻译原则 +## 翻译原则 技术文档的翻译首要原则是准确,但在准确的前提下如何保证『信、达、雅』?这是个挑战,在翻译本教程时,在某些情况下会根据上下文进行意译,便于中文读者阅读。 最后,感谢翻译者的无私贡献。❤️️ -# 离线阅读 +## 离线阅读 在本仓库的 [release 页面](https://github.com/zigcc/zigcc.github.io/releases)会定期将本教程导出为 PDF 格式,读者可按需下载。 @@ -49,7 +49,7 @@ ``` -# 其他学习资料 +## 其他学习资料 由于 Zig 目前还处于快速迭代,因此最权威的资料无疑是官方的 [Zig Language Reference](https://ziglang.org/documentation/master/),遇到语言的细节问题,基本都可以在这里找到答案。 其次是社区的一些高质量教程,例如: diff --git a/content/monthly/202207.smd b/content/monthly/202207.smd index 8314a95..eaa0323 100644 --- a/content/monthly/202207.smd +++ b/content/monthly/202207.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 观点/教程 +## 观点/教程 - [Zig 初体验 - Keep Coding](https://liujiacai.net/blog/2022/07/16/zig-intro/) @@ -28,7 +28,7 @@ before starting with Zig? : Zig](https://www.reddit.com/r/Zig/comments/w63x6r/is_it_necessary_to_know_c_or_another_systems/) -# 项目/工具 +## 项目/工具 - [Release bun v0.1.5 · oven-sh/bun](https://github.com/oven-sh/bun/releases/tag/bun-v0.1.5) @@ -47,7 +47,7 @@ - [How I built zig-sqlite](https://rischmann.fr/blog/how-i-built-zig-sqlite) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-07-01..2022-08-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-07-01..2022-08-01) - [std 文档展示更全面](https://twitter.com/croloris/status/1550955321694330880) diff --git a/content/monthly/202208.smd b/content/monthly/202208.smd index 7d64c67..11bdf55 100644 --- a/content/monthly/202208.smd +++ b/content/monthly/202208.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 观点/教程 +## 观点/教程 - [Growing a {{mustache}} with Zig](https://zig.news/batiati/growing-a-mustache-with-zig-di4) @@ -29,7 +29,7 @@ devlog](https://devlog.hexops.com/2022/packed-structs-in-zig/)。使用 Zig 的 `packet struct` 实现 bit set 功能 -# 项目/工具 +## 项目/工具 - [Virtual tables by vrischmann · Pull Request \#100 · vrischmann/zig-sqlite](https://github.com/vrischmann/zig-sqlite/pull/100)。zig-sqlite @@ -47,7 +47,7 @@ ![](/images/blockly.webp) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-08-01..2022-09-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-08-01..2022-09-01) - [make self-hosted the default compiler by andrewrk · Pull Request \#12368](https://github.com/ziglang/zig/pull/12368) Master diff --git a/content/monthly/202209.smd b/content/monthly/202209.smd index 37db874..92caedd 100644 --- a/content/monthly/202209.smd +++ b/content/monthly/202209.smd @@ -6,7 +6,7 @@ .draft = false, --- -# Zig VS Rust 火花 +## Zig VS Rust 火花 在 9/10 号左右,在 Twitter 上牵起了一小波关于 Zig VS Rust 的小火花,以至于最后 Zig 创始人 Andrew Kelley @@ -16,7 +16,7 @@ Let us exist。这里稍微整理下这件事情的过程: 本次事件主要 - Rust 核心贡献者: Patrick Walton - Zig 社区 VP: Loris Cro -## 时间线 +### 时间线 - 8/26 号,一篇关于 wasm 2 Game Jam 的[分析报告](https://wasm4.org/blog/jam-2-results/)中,使用 Zig @@ -57,7 +57,7 @@ Let us exist。这里稍微整理下这件事情的过程: 本次事件主要 这里[提到的](https://github.com/tigerbeetledb/tigerbeetle/blob/main/docs/DESIGN.md)。而且即便内存安全,也可能发生 OOM -## 总结 +### 总结 上面的链接比较多,这里稍微总结下这次争论的问题: @@ -69,7 +69,7 @@ Zig,笔者觉得大概率就是本次争论的观点,Rust 过于复杂,导致程序员不仅仅要考虑业务行为,还需要按照 Rust 的风格来编程,这加剧了程序出错的可能性。 -# 观点/教程 +## 观点/教程 - [How (memory) safe is zig?](https://www.scattered-thoughts.net/writing/how-safe-is-zig/) @@ -102,7 +102,7 @@ Zig,笔者觉得大概率就是本次争论的观点,Rust - [Zig ⚡ Improving the User Experience for Unused Variables](https://vimeo.com/748218307) -# 项目/工具 +## 项目/工具 - [Zig 开发常用类库](https://github.com/zigcc/forum/discussions/28),如果读者的 @@ -126,4 +126,4 @@ Zig,笔者觉得大概率就是本次争论的观点,Rust - [stm32f4.zig](https://github.com/moonxraccoon/stm32f4.zig) STM32F4(ARM Cortex M4 的高性能 32 位微控制器) 固件抽象层 -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?q=+is%3Aclosed+is%3Apr+closed%3A2022-09-01..2022-10-01+) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?q=+is%3Aclosed+is%3Apr+closed%3A2022-09-01..2022-10-01+) diff --git a/content/monthly/202210.smd b/content/monthly/202210.smd index 6e0b0dc..8acd311 100644 --- a/content/monthly/202210.smd +++ b/content/monthly/202210.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 观点/教程 +## 观点/教程 - [Zig Is Self-Hosted Now, What’s Next? | Loris Cro’s Blog](https://kristoff.it/blog/zig-self-hosted-now-what/) | 0.10 @@ -39,7 +39,7 @@ - \[音频\] [005. 与 LemonHX 畅聊新一代编程语言 Zig – RustTalk](https://rusttalk.github.io/podcast/005/) -# 项目/工具 +## 项目/工具 - [Himujjal/zig-json5](https://github.com/Himujjal/zig-json5): A JSON5 parser/stringifier for Zig resembling the std.json API @@ -55,4 +55,4 @@ - [A minimal RocksDB example with Zig](https://notes.eatonphil.com/zigrocks.html) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-10-01..2022-11-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-10-01..2022-11-01) diff --git a/content/monthly/202211.smd b/content/monthly/202211.smd index e9f68be..0c18211 100644 --- a/content/monthly/202211.smd +++ b/content/monthly/202211.smd @@ -6,7 +6,7 @@ .draft = false, --- -# [0.10.0 Release Notes](https://ziglang.org/download/0.10.0/release-notes.html) +## [0.10.0 Release Notes](https://ziglang.org/download/0.10.0/release-notes.html) 本月最大的事情就是 0.10 版本发布了,主要功能就是 self-hosted compiler,也称为『自举』,即可以用 Zig 来写 Zig @@ -17,13 +17,13 @@ compiler,也称为『自举』,即可以用 Zig 来写 Zig 赶紧升级吧,少年! -# zigcc 中文社区微信群 +## zigcc 中文社区微信群 欢迎喜欢 Zig 的小伙伴加入! {{\< figure src=“https://github.com/zigcc/.github/raw/main/weixin.jpg” width=“200” title=“ZigCC 微信群二维码” \>}} -# 观点/教程 +## 观点/教程 - [Wasmer 3.0 使用 Zig 进行跨平台编译 · Discussion \#35 · zigcc/forum](https://github.com/zigcc/forum/discussions/35) @@ -98,7 +98,7 @@ const Animal = union(enum){ [spoon](https://sr.ht/~leon_plickat/zig-spoon/) 这个库开发了一个帮助自己进行图片打标的 UI 工具, -# 项目/工具 +## 项目/工具 - [Zig 程序设计语言中文手册](https://sxwangzhiwen.github.io/zigcndoc/zigcndoc.html) @@ -114,4 +114,4 @@ const Animal = union(enum){ - [Zig Support plugin for IntelliJ and CLion version 0.2.0 released](https://zig.news/marioariasc/zig-support-plugin-for-intellij-and-clion-version-020-released-3g06) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-11-01..2022-12-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-11-01..2022-12-01) diff --git a/content/monthly/202212.smd b/content/monthly/202212.smd index a9943e1..7a32c91 100644 --- a/content/monthly/202212.smd +++ b/content/monthly/202212.smd @@ -6,11 +6,11 @@ .draft = false, --- -# 观点/教程 +## 观点/教程 - [Goodbye to the C++ Implementation of Zig ⚡ Zig Programming Language](https://ziglang.org/news/goodbye-cpp/) -# 项目/工具 +## 项目/工具 -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-12-01..2023-01-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-12-01..2023-01-01) diff --git a/content/monthly/202301.smd b/content/monthly/202301.smd index 8d1419e..3cb3124 100644 --- a/content/monthly/202301.smd +++ b/content/monthly/202301.smd @@ -6,7 +6,7 @@ .draft = false, --- -# [0.10.1](https://ziglang.org/download/0.10.1/release-notes.html) 版本发布 +## [0.10.1](https://ziglang.org/download/0.10.1/release-notes.html) 版本发布 一个小版本,主要是 bugfix。最主要的功能是:[Package Manager MVP](https://github.com/ziglang/zig/pull/14265),Zig @@ -59,7 +59,7 @@ pub fn build(b: *std.build.Builder) void { [15.0.7](http://releases.llvm.org/15.0.7/docs/ReleaseNotes.html) - 是 0.10.x 的最后一个 release 版本 -# 观点/教程 +## 观点/教程 [Code study: interface idioms/patterns in zig standard libraries](https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj) @@ -77,7 +77,7 @@ Beetle](https://datastackshow.com/podcast/why-accounting-needs-its-own-database- Zig](https://0110.be/posts/Crossplatform_JNI_builds_with_Zig) 又一个使用 Zig 作为交叉编译的例子 -# 项目/工具 +## 项目/工具 [Introducing ⚡zap⚡ - blazingly fast backends in zig](https://zig.news/renerocksai/introducing-zap-blazingly-fast-backends-in-zig-3jhh) @@ -98,4 +98,4 @@ Zig library for HyperLogLog estimation [This Week In Zig](https://thisweekinzig.mataroa.blog/) 一个介绍 Zig 的周刊,主要是 master 分支上的改动 -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-01-01..2023-02-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-01-01..2023-02-01) diff --git a/content/monthly/202302.smd b/content/monthly/202302.smd index 7083760..33acb29 100644 --- a/content/monthly/202302.smd +++ b/content/monthly/202302.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 包管理器进展 +## 包管理器进展 包管理器自 [\#14265](https://github.com/ziglang/zig/pull/14265) 合并后一直在不断推进,以下两个是最主要的改变: @@ -61,7 +61,7 @@ 也欢迎大家给自己熟悉的 C/C++ 项目提 PR 让其支持 zig build,让构建不再那么痛苦。 -# 观点/教程 +## 观点/教程 - [How a Zig IDE Could Work](https://matklad.github.io/2023/02/10/how-a-zig-ide-could-work.html) @@ -105,9 +105,9 @@ build,让构建不再那么痛苦。 - [Smoking Hot Binary Search In Zig](https://blog.deckc.hair/2023-02-22-smoking-hot-binary-search-in-zig.html) -# 项目/工具 +## 项目/工具 - [Writing high-performance clients for TigerBeetle](https://tigerbeetle.com/blog/2023-02-21-writing-high-performance-clients-for-tigerbeetle/) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-02-01..2023-03-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-02-01..2023-03-01) diff --git a/content/monthly/202303.smd b/content/monthly/202303.smd index 44ab59c..7c9af36 100644 --- a/content/monthly/202303.smd +++ b/content/monthly/202303.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 观点/教程 +## 观点/教程 [Creating arbitrary error values by using error.Something syntax](https://www.reddit.com/r/zig/comments/11wmoky) @@ -61,7 +61,7 @@ Nix](https://flyx.org/cross-packaging/) [Zig And Rust](https://matklad.github.io/2023/03/26/zig-and-rust.html) -# 项目/工具 +## 项目/工具 [macovedj/doink](https://github.com/macovedj/doink) Making WebAssembly Components with Zig @@ -80,7 +80,7 @@ Tiny desktop utility to keep notes [ringtailsoftware/zig-wasm-audio-framebuffer](https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer) Examples of integrating Zig and Wasm for audio and graphics on the web -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-03-01..2023-04-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-03-01..2023-04-01) [zig build: run steps in parallel \#14647](https://github.com/ziglang/zig/pull/14647) @@ -98,7 +98,7 @@ bug,一个影响比较大的是 test 的构建方式不一样了。 现在 `addTest` 和 `addExecutable` 一样,输出都是 `CompileStep` ,它默认不会执行,需要调用 `run()` 拿到 run step 才可以。 -# Zig 构建系统介绍 +## Zig 构建系统介绍 构建系统其实是 Zig 生态中比较重要的一环,和 Rust 不同,Zig 即是一门编程语言,也是一系列工具链,比如编译 Zig 时,没有 zigc diff --git a/content/monthly/202304.smd b/content/monthly/202304.smd index 73900f2..a933692 100644 --- a/content/monthly/202304.smd +++ b/content/monthly/202304.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 重大事件 +## 重大事件 在 2023 四月份的 [Tiobe](https://www.tiobe.com/tiobe-index/) 指数上,Zig [排名 @@ -29,7 +29,7 @@ Loris 发推表示这个数字对 Zig \> -# 观点/教程 +## 观点/教程 [When should I use an UNTAGGED Union?](https://zig.news/kristoff/when-should-i-use-an-untagged-union-56ek) @@ -78,7 +78,7 @@ Systems Distributed 23 视频,[B 站链接](https://www.bilibili.com/video/BV1gP41117zY/),作者博客:[Scattered Thoughts](https://www.scattered-thoughts.net/) -# 项目/工具 +## 项目/工具 [Coming Soon to a Zig Near You: HTTP Client](https://zig.news/nameless/coming-soon-to-a-zig-near-you-http-client-5b81) @@ -106,4 +106,4 @@ Zvisor is an open-source hypervisor written in the Zig programming language, which provides a modern and efficient approach to systems programming. -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) diff --git a/content/monthly/202305.smd b/content/monthly/202305.smd index c64d4e5..99858fc 100644 --- a/content/monthly/202305.smd +++ b/content/monthly/202305.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 重大事件 +## 重大事件 这个月主要的事情就是 HTTP server 在标准库的增加了,具体可参考: @@ -15,7 +15,7 @@ - [http server in the standard library · Issue \#910](https://github.com/ziglang/zig/issues/910) -# 观点/教程 +## 观点/教程 [Integrating Zig and SwiftUI](https://mitchellh.com/writing/zig-and-swiftui) @@ -67,7 +67,7 @@ System](https://www.priver.dev/blog/zig/initial-commit-build-system/) [Writing DNS resolver in Zig](https://e-aakash.github.io/update/2023/06/04/resolv-dns-resolver-in-zig.html) -# 项目/工具 +## 项目/工具 [Zig by Example](https://zigbyexample.github.io/) 非常好的学习资料。Learn How to use Zig’s Standard Library, by small @@ -100,4 +100,4 @@ A 2048 game to run in terminal [mitchellh/zig](https://github.com/mitchellh/zig-objc) Objective-C runtime bindings for Zig (Zig calling ObjC). -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) diff --git a/content/monthly/202306.smd b/content/monthly/202306.smd index d15b430..6960230 100644 --- a/content/monthly/202306.smd +++ b/content/monthly/202306.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 重大事件 +## 重大事件 一个是这个:[The Zig subreddit has closed](https://ziggit.dev/t/the-zig-subreddit-has-closed/679),现在 @@ -91,7 +91,7 @@ Zig 的各种 backend 进展,能不能给 fix 几个 regression?! 最后,即便 Zig 这个项目夭折了,我相信通过这个学习的过程也有助于提高我们对系统编程、编译器的理解,不是吗? -# 观点/教程 +## 观点/教程 [A Note About Zig Books for the Zig Community](https://kristoff.it/blog/note-about-zig-books/) @@ -147,14 +147,14 @@ segfaults](https://www.openmymind.net/Zig-Danling-Pointers/) 讨论](https://news.ycombinator.com/item?id=36149462) -# 项目/工具 +## 项目/工具 [pondzdev/duckdb](https://github.com/pondzdev/duckdb-proxy/) 一个将 DuckDB 数据库通过 HTTP API 暴露出来的代理,主要是利用了 [DuckDB C API](https://duckdb.org/docs/api/c/api.html),示例: ``` bash -# open the database in readonly (DB must exist in this case) +## open the database in readonly (DB must exist in this case) $ ./duckdb-proxy --readonly db/mydatabase.duckdb $ curl http://localhost:8012/api/1/exec \ @@ -184,7 +184,7 @@ Shell completions for the Zig compiler. [menduz/zig](https://github.com/menduz/zig-steamworks) Steamwork bindings for Zig. -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-05-01..2023-06-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-05-01..2023-06-01) - [WASI: Implement experimental threading support by Luukdegram · Pull Request \#16207 · diff --git a/content/monthly/202307.smd b/content/monthly/202307.smd index e0b00e5..b1d3ac9 100644 --- a/content/monthly/202307.smd +++ b/content/monthly/202307.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 重大事件 +## 重大事件 Andrewk 在最新的文章 [The Upcoming Release Postponed Two More Weeks and Lacks Async Functions](https://ziglang.org/news/0.11.0-postponed-again/) @@ -24,7 +24,7 @@ Team](https://ziglang.org/news/welcome-jacob-young/),Core Team 恭喜 Core Team,又添一虎将! -# 观点/教程 +## 观点/教程 [Copy Hunting | TigerBeetle](https://tigerbeetle.com/blog/2023-07-26-copy-hunting/) 比较有意思的文章,通过分析 LLVM 的 IR,来避免程序中不必要的 memcpy @@ -129,7 +129,7 @@ TigerBeetle 的新花样,把数据库搬到了 Web 上。[HN 比较有意思的文章,作者比较了 Clang、Zig 对相同逻辑代码生产的 LLVM IR,Zig 生成的 IR 更加精简 -# 项目/工具 +## 项目/工具 [Zig helped us move data to the Edge. Here are our impressions](https://blog.turso.tech/zig-helped-us-move-data-to-the-edge-here-are-our-impressions-67d3a9c45af4) [Turso](https://turso.tech/) @@ -147,4 +147,4 @@ Experimental operating system written in Zig [EugenHotaj/ziggpt2](https://github.com/EugenHotaj/zig_gpt2) GPT-2 inference engine written in Zig -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-06-01..2023-07-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-06-01..2023-07-01) diff --git a/content/monthly/202308.smd b/content/monthly/202308.smd index f00cb42..bff5532 100644 --- a/content/monthly/202308.smd +++ b/content/monthly/202308.smd @@ -6,12 +6,12 @@ .draft = false, --- -# [0.11 正式发布](https://ziglang.org/download/0.11.0/release-notes.html#x86-Backend) +## [0.11 正式发布](https://ziglang.org/download/0.11.0/release-notes.html#x86-Backend) 0.11 终于在 8 月 4 号释出了,下面来看看它的一些重要改进吧。[HN 讨论](https://news.ycombinator.com/item?id=36995735) -# Peer Type Resolution Improvements +## Peer Type Resolution Improvements 对等类型解析算法得到改进,下面是一些在 0.10 中不能解析,但在 0.11 中可以解析的例子: @@ -29,7 +29,7 @@ 而且现在使用 `@intCast` 这类 builtin 都只接受一个参数,目前类型根据上下文自动推断出来。 -# Multi-Object For Loops +## Multi-Object For Loops 可以同时对多个对象进行遍历: @@ -45,7 +45,7 @@ for (input, 0..) |x, i| { } ``` -# @min and @max +## @min and @max 主要有两个改动: @@ -65,7 +65,7 @@ test "@min/@max refines result type" { } ``` -# @inComptime +## @inComptime 新加的 builtin,用于判断执行是否在 comptime 环境下执行: @@ -95,12 +95,12 @@ test "@inComptime" { } ``` -# 类型转化相关 builtin 的重命名 +## 类型转化相关 builtin 的重命名 之前 `@xToY` 形式的 builtin 现在已经改成了 `@yFromX` ,这样的主要好处是便于阅读(从右向左),这是[草案](https://github.com/ziglang/zig/issues/6128)。 -# Tuple 类型声明 +## Tuple 类型声明 现在可以直接用无 field 名字的 struct 来声明 tuple 类型: @@ -133,7 +133,7 @@ test "tuple declarations" { + const testcases = [_]struct { []const u8, []const u8, bool }{ ``` -# 排序 +## 排序 现在排序算法分布两类: @@ -143,7 +143,7 @@ test "tuple declarations" { 与堆排的快速最差情况相结合,同时在具有特定模式的输入上达到线性时间。 -# Stack Unwinding +## Stack Unwinding Zig 之前依赖 [frame pointer](https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers) @@ -154,20 +154,20 @@ pointer](https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers) DWARF unwind tables 和 MachO compact unwind information 来会恢复堆栈,详见:[\#15823](https://github.com/ziglang/zig/pull/15823)。 -# 包管理 +## 包管理 0.11 首次正式引入了包管理器,具体解释可以参考:[Zig Build System](https://en.liujiacai.net/2023/04/13/zig-build-system/),而且很重要一点,step 之间可以[并发执行](https://ziglang.org/download/0.11.0/release-notes.html#Steps-Run-In-Parallel), -# Bootstrapping +## Bootstrapping C++ 实现的 Zig 编译器已经被彻底移除,这意味着 `-fstage1` 不再生效,Zig 现在只需要一个 2.4M 的 WebAssembly 文件和一个 C 编辑器即可,工作细节可以参考:[Goodbye to the C++ Implementation of Zig](https://ziglang.org/news/goodbye-cpp/)。 -# 代码生成 +## 代码生成 虽然 Zig 编译器现在还是主要使用 LLVM 来进行代码生成,但在这次发布中,其他几个后端也有了非常大的进步: @@ -182,7 +182,7 @@ Zig](https://ziglang.org/news/goodbye-cpp/)。 后端专注于为 OpenCL 内核生成代码,不过未来可能也会支持兼容 Vulkan 的着色器。 -# 增量编译 +## 增量编译 虽然这仍是一个高度 WIP 的功能,但这一版本周期中的许多改进为编译器的增量编译功能铺平了道路。其中最重要的是 @@ -190,7 +190,7 @@ Zig](https://ziglang.org/news/goodbye-cpp/)。 用户大多看不到这一改动,但它为编译器带来了许多好处,其中之一就是我们现在更接近增量编译了。增量编译将是 0.12.0 发布周期的重点。 -# 观点/教程 +## 观点/教程 [Error Handling In Zig](https://www.aolium.com/karlseguin/4013ac14-2457-479b-e59b-e603c04673c8) 又一篇讨论错误处理的文章 @@ -222,9 +222,9 @@ Andrew 的最新文章,远离社交平台! [Taking off with Zig: Putting the Z in Benchmark — Double Trouble](https://double-trouble.dev/post/zbench/) -# 项目/工具 +## 项目/工具 - [Mach v0.2 released](https://devlog.hexops.com/2023/mach-v0.2-released/) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-07-01..2023-08-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-07-01..2023-08-01) diff --git a/content/monthly/202309.smd b/content/monthly/202309.smd index c5afb6f..ea627d7 100644 --- a/content/monthly/202309.smd +++ b/content/monthly/202309.smd @@ -6,9 +6,9 @@ .draft = false, --- -# 重大事件 +## 重大事件 -# [Bounties Damage Open Source Projects](https://ziglang.org/news/bounties-damage-open-source-projects/) +## [Bounties Damage Open Source Projects](https://ziglang.org/news/bounties-damage-open-source-projects/) 在 2023-09-11 号,Wasmerio CEO 创建了 [Support WASIX · Issue \#17115](https://github.com/ziglang/zig/issues/17115),表示想赞助 Zig @@ -23,7 +23,7 @@ Andrew 与 Loris 在这篇文章中主要阐述了这么做为什么是伤害社 社区,他更想保证 Zig 的独立性,这也是他们创办 [Software You Can Love](https://kristoff.it/blog/the-open-source-game/) 的初衷。 -# [Bun 1.0](https://bun.sh/blog/bun-v1.0) +## [Bun 1.0](https://bun.sh/blog/bun-v1.0) 面包终于出炉了!Bun 毫无疑问是 Zig 的明星项目,它在 2023-09-08 正式发布了 1.0 版本,这对开发者来说可能没什么太大的区别,毕竟就只是个 @@ -38,7 +38,7 @@ Bun 并不简单的是个 Node.js 替代品,而是大而全的工具链: - Testing libraries,内置 test runner,支持快照测试、模拟和代码覆盖,可以替代:jest、ts-test -# 观点/教程 +## 观点/教程 [Kiesel Devlog \#1: Now passing 25% of test262](https://linus.dev/posts/kiesel-devlog-1/) 另一个 devlog,作者写了一个 JS engine 用来学习 Zig,该作者是 SerenityOS @@ -85,7 +85,7 @@ Mitchell Hashimoto 在这篇文章里分享了开发终端 Ghostty 时用的 Zig [SoA 内存布局]($image.siteAsset('images/soa-layout.webp')) -# 项目/工具 +## 项目/工具 [ZigBrains](https://plugins.jetbrains.com/plugin/22456-zigbrains) A multifunctional Zig Programming Language plugin for the IDEA platform. @@ -114,7 +114,7 @@ Reactive signal/Dependency tracking library in Zig. [buzz-language/buzz](https://github.com/buzz-language/buzz) A small/lightweight statically typed scripting language -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-08-01..2023-09-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-08-01..2023-09-01) [Aro translate](https://github.com/ziglang/zig/pull/17221) 用 Zig 写的 [Aro](https://github.com/Vexu/arocc) 来替换 clang,来实现 diff --git a/content/monthly/202310.smd b/content/monthly/202310.smd index fa51abd..ef821dc 100644 --- a/content/monthly/202310.smd +++ b/content/monthly/202310.smd @@ -6,9 +6,9 @@ .draft = false, --- -# 重大事件 +## 重大事件 -# 观点/教程 +## 观点/教程 [Notes From the Field: Learning Zig](https://registerspill.thorstenball.com/p/notes-from-the-field-learning-zig) Zig 初学者的使用经验分享 @@ -96,7 +96,7 @@ pub fn main() !void { 由于 Zig 还在快速开发迭代中,因此项目很有可能出现新版本 Zig 无法编译的情况,这篇文章介绍了一些管理多个 Zig 版本的方式。 -# 项目/工具 +## 项目/工具 [zigcli](https://zigcli.liujiacai.net/) a toolkit for building command lines programs in Zig @@ -115,4 +115,4 @@ Enable the use of Zig code in JavaScript project Language server for GLSL (autocomplete, goto-definition, formatter, and more) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-09-01..2023-10-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-09-01..2023-10-01) diff --git a/content/monthly/202311.smd b/content/monthly/202311.smd index 686a487..3845eea 100644 --- a/content/monthly/202311.smd +++ b/content/monthly/202311.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 重大事件 +## 重大事件 本月讨论比较多的就是 [Zig May Pass Anything By Reference](https://www.1a-insec.net/blog/25-zig-reference-semantics/) @@ -58,7 +58,7 @@ precisely』,目前来看确实是这样的,而且 core team - [Design flaw: Swapping struct fields yields unexpected value \#12064](https://github.com/ziglang/zig/issues/12064) -# 观点/教程 +## 观点/教程 [Zig's std.json.Parsed(T)](https://www.openmymind.net/Zigs-std-json-Parsed/) 老朋友 openmymind 的文章,这篇文章主要讲述了使用 Zig 中的 json @@ -147,7 +147,7 @@ Fields - 一个包的兼容性,应该由文档来解释 - 增加私有字段,会增加语言的复杂度,而且这种复杂性本身是完全可以避免的 -# 项目/工具 +## 项目/工具 [zig build explained – building C/C++ projects](https://zig.news/xq/zig-build-explained-part-2-1850) 经典文章回顾,如何使用 Zig 构建系统编译 C/C++ 项目 @@ -155,4 +155,4 @@ Fields [akhildevelops/cudaz](https://github.com/akhildevelops/cudaz) A Zig Cuda wrapper -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-10-01..2023-11-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-10-01..2023-11-01) diff --git a/content/monthly/202402.smd b/content/monthly/202402.smd index 5c71143..c5934dc 100644 --- a/content/monthly/202402.smd +++ b/content/monthly/202402.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 重大事件 +## 重大事件 ($section.id('major-events')) Andrew 最近在 zigshow 节目中介绍了 Zig 2024 年的规划,主要有以下几点: @@ -32,7 +32,7 @@ Andrew 最近在 zigshow 节目中介绍了 Zig 2024 年的规划,主要有以 \#91](https://github.com/orgs/zigcc/discussions/91) B 站搬运地址:https://www.bilibili.com/video/BV1UC4y167w9/ -# 观点/教程 +## 观点/教程 ($section.id('opinion-tutorial')) [Fast-growing Zig tops Stack Overflow survey for highest-paid programming language](https://www.infoworld.com/article/3713082/fast-growing-zig-tops-stack-overflow-survey-for-highest-paid-programming-language.html) 估计是 Bun 带动的贫富差距?! @@ -67,7 +67,7 @@ Syndica 公司的 Sig,这是一款用 Zig 编写的、专注于 RPS 的 Solana - [Senior Software Engineer (Zig/C/Rust) @ Syndica](https://jobs.ashbyhq.com/syndica/15ab4e32-0f32-41a0-b8b0-16b6518158e9) -# 项目/工具 +## 项目/工具 ($section.id('projects-tools')) [semickolon/kirei](https://github.com/semickolon/kirei) 🌸 The prettiest keyboard software @@ -94,4 +94,4 @@ Zig-TypeScript binding generator 🟦 🦎 [sneekyfoxx/ziggy](https://github.com/sneekyfoxx/ziggy) 又又一个 Zig 版本管理工具 -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2023-03-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2023-03-01) ($section.id('zig-updates-202402')) diff --git a/content/monthly/202403.smd b/content/monthly/202403.smd index 47aa9f1..da86842 100644 --- a/content/monthly/202403.smd +++ b/content/monthly/202403.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 重大事件 +## 重大事件 ($section.id('major-events')) > @@ -39,7 +39,7 @@ Kelley](https://rustacean-station.org/episode/andrew-kelley/) 也会紧密关注发布动态,有消息第一时间分享给大家。耐不住寂寞的朋友,可以先去刷刷 Zig 的 discord。 -# 观点/教程 +## 观点/教程 ($section.id('opinion-tutorial')) [Redesign How Autodoc Works](https://github.com/ziglang/zig/pull/19208) Andrew 在这个 PR 里重构了现有的文档系统 @@ -235,7 +235,7 @@ const port = port: { [Using Zig with WebAssembly](https://blog.mjgrzymek.com/blog/zigwasm) 如何将 Zig 编译成 wasm,并传递复杂的参数。 -# 项目/工具 +## 项目/工具 ($section.id('projects-tools')) [xataio/pgzx](https://github.com/xataio/pgzx) Create PostgreSQL extensions using Zig. 一个例子: @@ -291,4 +291,4 @@ ability of running pipeline like bash. [zigcc/zig-milestone](https://github.com/zigcc/zig-milestone) Zig milstone monitor -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2024-03-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2024-03-01) ($section.id('zig-updates-202403')) diff --git a/content/monthly/202404.smd b/content/monthly/202404.smd index 3c477f8..b7d6216 100644 --- a/content/monthly/202404.smd +++ b/content/monthly/202404.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 重大事件 +## 重大事件 ($section.id('major-events')) 千呼万唤的 0.12.0 版本终于 2024-04-20 正式释出了!这次版本历时 8 个月,有 268 位贡献者,一共进行了 3688 次提交!社区内的一些讨论:[Hacker @@ -37,7 +37,7 @@ CMake、Makefile、Vcpkg、Git submodule 等工具,所有的依赖使用 zon 期待一年后 Zig 的生态! -# 观点/教程 +## 观点/教程 ($section.id('opinion-tutorial')) [Zig 中任意精度整数用途与实现](https://github.com/zigcc/forum/issues/112) 由于 CPU @@ -104,7 +104,7 @@ Vector,到最后利用 bit 的特点,来逐步优化,并用 godbolt [Documentation takes another step backwards : r/Zig](https://www.reddit.com/r/Zig/comments/1cc1x2v/documentation_takes_another_step_backwards/) 一个 Reddit 用户对文档的抱怨 -# 项目/工具 +## 项目/工具 ($section.id('projects-tools')) [rofrol/zig-companies](https://github.com/rofrol/zig-companies) A list of companies using Zig in production. @@ -124,4 +124,4 @@ Zig string library that includes small string optimization on the stack [FalsePattern/ZigBrains](https://github.com/FalsePattern/ZigBrains) Yet another zig language plugin for intellij -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-04-01..2024-05-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-04-01..2024-05-01) ($section.id('zig-updates-202404')) diff --git a/content/monthly/202405.smd b/content/monthly/202405.smd index d12b346..7ea076b 100644 --- a/content/monthly/202405.smd +++ b/content/monthly/202405.smd @@ -6,14 +6,14 @@ .draft = false, --- -# 观点/教程 +## 观点/教程 ($section.id('opinion-tutorial')) -# [Thoughts on Zig](https://arne.me/blog/thoughts-on-zig) +## [Thoughts on Zig](https://arne.me/blog/thoughts-on-zig) ($section.id('thoughts-on-zig')) 又一篇 Zig 初学者的使用体验文档,如果你也在犹豫要不要学 Zig,这是个不错的经验参考。 -# [I'm sold on Zig's simplicity : r/Zig](https://www.reddit.com/r/Zig/comments/1ckstjv/im_sold_on_zigs_simplicity/) +## [I'm sold on Zig's simplicity : r/Zig](https://www.reddit.com/r/Zig/comments/1ckstjv/im_sold_on_zigs_simplicity/) ($section.id('zig-simplicity')) 一个具有资深经验开发者,在这里描述了自己选择业余项目语言的经历: @@ -32,7 +32,7 @@ Zig,这是个不错的经验参考。 - 唯一缺失的就是『接口』,但这一点并不是很关键,就像在 C里也没有,但是 C 可以做任何事 -# [Zig's New CLI Progress Bar Explained](https://andrewkelley.me/post/zig-new-cli-progress-bar-explained.html) +## [Zig's New CLI Progress Bar Explained](https://andrewkelley.me/post/zig-new-cli-progress-bar-explained.html) ($section.id('zig-cli-progress-bar')) Andrew 的一篇文章,讲述了在最新版的 Zig 中,对进度条的改进实现,现在的进度展示更加友好。 @@ -42,7 +42,7 @@ Andrew 的一篇文章,讲述了在最新版的 Zig - 首先通过预先分配好需要使用的结构,保证后续无需在进行 heap 申请 - 通过 atomic 操作来实现一个无锁的 freelist,用于申请、释放 Node -# [Writing a task scheduler in Zig](https://www.openmymind.net/Writing-a-Task-Scheduler-in-Zig/) +## [Writing a task scheduler in Zig](https://www.openmymind.net/Writing-a-Task-Scheduler-in-Zig/) ($section.id('task-scheduler-zig')) Openmymind 作者的又一力作,通过编写一个任务调度器,讲述了多线程编程的基本要领: @@ -81,7 +81,7 @@ Openmymind } ``` -# 项目/工具 +## 项目/工具 ($section.id('projects-tools')) [zigar](https://github.com/chung-leong/zigar) Enable the use of Zig code in JavaScript project。它可以让你直接在 JS @@ -96,4 +96,4 @@ A generic and general purpose Set implementation for the Zig language [akarpovskii/tuile](https://github.com/akarpovskii/tuile) A Text UI library for Zig -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-05-01..2024-06-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-05-01..2024-06-01) ($section.id('zig-updates-202405')) diff --git a/content/monthly/202406.smd b/content/monthly/202406.smd index b3c0de5..b9671fb 100644 --- a/content/monthly/202406.smd +++ b/content/monthly/202406.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 重大事件 +## 重大事件 ($section.id('major-events')) 2024-06-07,0.13.0 发布,历时不足 2 个月,有 73 位贡献者,一共进行了 415 次提交! 这是一个相对较短的发布周期,主要原因是工具链升级,例如升级到 @@ -29,9 +29,9 @@ const map = std.StaticStringMap(T).initComptime(kvs_list); - 启用增量编译以实现快速重建。 - 将并发引入语义分析,进一步提高编译速度。 -# 观点/教程 +## 观点/教程 ($section.id('opinion-tutorial')) -# [Leveraging Zig's Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/) +## [Leveraging Zig's Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/) ($section.id('zig-allocators')) 老朋友 openmymind 的又一篇好文章:如何利用 Zig 的 Allocator 来实现请求级别的内存分配。 Zig Allocator @@ -98,11 +98,8 @@ fn run(worker: *Worker) void { worker.write(conn.res); } } -``` - -# [On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/) -- +## [On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/) ($section.id('zig-vs-rust-at-work')) 这篇文章作者描述了所在公司在改造老 C/C++ 项目时,为什么选择了 Zig 而不是 Rust。 重写的项目运行在多个平台上(Web、移动端、VR @@ -131,14 +128,14 @@ Rust。 重写的项目运行在多个平台上(Web、移动端、VR 相信这也是大部分人选择 Zig 的原因:简洁、高效。 -# [Packing some Zig before going for the countryside](https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/) +## [Packing some Zig before going for the countryside](https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/) ($section.id('packing-zig-countryside')) 作者列举的一些 Zig 学习资料、常用类库。该作者的另一篇文章也有不少资料:[2024 Collection of Zig resources](https://ludwigabap.bearblog.dev/2024-collection-of-zig-resources/) -# [Why I am not yet ready to switch to Zig from Rust](https://turso.tech/blog/why-i-am-not-yet-ready-to-switch-to-zig-from-rust) +## [Why I am not yet ready to switch to Zig from Rust](https://turso.tech/blog/why-i-am-not-yet-ready-to-switch-to-zig-from-rust) ($section.id('not-switching-to-zig')) Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较喜欢 C,但 C 不是一门安全的语言,因此通过 Rust,作者可以避免 写出 SIGSEGVS @@ -159,7 +156,7 @@ Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较 其他社区的一些讨论:[Lobsters](https://lobste.rs/s/0mnhdx)、[Hacker News](https://news.ycombinator.com/item?id=40681862) -# 项目/工具 +## 项目/工具 ($section.id('projects-tools')) [malcolmstill/zware](https://github.com/malcolmstill/zware) Zig WebAssembly Runtime Engine @@ -168,4 +165,4 @@ Zig WebAssembly Runtime Engine iouring like asynchronous API and coroutine powered IO tasks for zig -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-06-01..2024-07-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-06-01..2024-07-01) ($section.id('zig-updates-202406')) diff --git a/content/monthly/202407.smd b/content/monthly/202407.smd index 61974c1..4248415 100644 --- a/content/monthly/202407.smd +++ b/content/monthly/202407.smd @@ -6,7 +6,7 @@ .draft = false, --- -# 重大事件 +## 重大事件 ($section.id('major-events')) 在[这篇文章](https://leaddev.com/tech/why-zig-one-hottest-programming-languages-learn)里,作者引用 Stackoverflow 2024 @@ -34,9 +34,9 @@ Zig 开发者的一些观点: 但与此同时,这些语言的工具链却非常糟糕。 Zig 允许用户涉猎这些核心编程语言,但可以使用更好的工具链,兼容各种语言和更丰富的功能。 -# 观点/教程 +## 观点/教程 ($section.id('opinion-tutorial')) -# [Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/) +## [Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/) ($section.id('improving-zls-experience')) Loris Cro 的最新文章,介绍了一个改进 Zig 编码体验的小技巧,十分推荐大家使用。具体来说是这样的: 通过配置 @@ -82,7 +82,7 @@ zls --config-path zls.json 这样不同的项目就可以用不同的检查步骤了。 -# [Systems Distributed '24](https://guergabo.substack.com/p/systems-distributed-24) +## [Systems Distributed '24](https://guergabo.substack.com/p/systems-distributed-24) ($section.id('systems-distributed-24')) 作者对这次会议的一个回顾总结,议题主要有如下几个方向: @@ -92,7 +92,7 @@ zls --config-path zls.json - Lessons from Building Distributed Databases - Notes from Water Cooler Chats -# [C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself](https://jstrieb.github.io/posts/c-reflection-zig/) +## [C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself](https://jstrieb.github.io/posts/c-reflection-zig/) ($section.id('c-macro-reflection-zig')) 该作者分享了利用 typeInfo 来在编译时获取字段名的能力,要知道,在 C 里面是没有这个功能的。 @@ -135,7 +135,7 @@ fn get_window_messages() [65536][:0]const u8 { } ``` -# [A TypeScripter's Take on Zig (Advent of Code 2023)](https://effectivetypescript.com/2024/07/17/advent2023-zig/) +## [A TypeScripter's Take on Zig (Advent of Code 2023)](https://effectivetypescript.com/2024/07/17/advent2023-zig/) ($section.id('typescript-take-advent2023-zig')) 以下该作者的一些心得体会: @@ -167,7 +167,7 @@ fn get_window_messages() [65536][:0]const u8 { - 在 JavaScript 允许您内联表达式的某些情况下,您可能需要分解出一个变量来澄清生存期。看看[这个问题](https://github.com/ziglang/zig/issues/12414)。 -# 项目/工具 +## 项目/工具 ($section.id('projects-tools')) [18alantom/fex](https://github.com/18alantom/fex) A command-line file explorer prioritizing quick navigation. @@ -175,4 +175,4 @@ A command-line file explorer prioritizing quick navigation. [griush/zm](https://github.com/griush/zm) SIMD Math library fully cross-platform -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-07-01..2024-08-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-07-01..2024-08-01) ($section.id('zig-updates-202407')) diff --git a/content/monthly/202410.smd b/content/monthly/202410.smd index 22258f7..e072a1f 100644 --- a/content/monthly/202410.smd +++ b/content/monthly/202410.smd @@ -6,9 +6,9 @@ .draft = false, --- -# 重大事件 +## 重大事件 ($section.id('major-events')) -# 向 Zig 软件基金会认捐 30 万美元 +## 向 Zig 软件基金会认捐 30 万美元 ($section.id('donation-zsf-300k')) Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金会 (ZSF) 捐赠了 300,000 美元。 @@ -25,9 +25,9 @@ Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金 作为其中的一部分,我们希望支持那些我们认为可以带来变革和影响的独立软件项目,这既是回馈给我如此之多的社区的一种方式,更重要的是,这也是彰显和鼓励为热爱而构建的文化的一种方式。 Zig 就是这样一个项目。 -# 观点/教程 +## 观点/教程 ($section.id('opinion-tutorial')) -# [Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/) +## [Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/) ($section.id('zig-vs-c')) 对 Zig 的特色进行了简单扼要的介绍,主要有: @@ -61,9 +61,9 @@ Zig 就是这样一个项目。 4. 与 C 无缝交互, `zig cc` 是交叉编译的首选 -# [Building Nintendo 3DS Homebrew with Zig](https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html) +## [Building Nintendo 3DS Homebrew with Zig](https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html) ($section.id('3ds-homebrew-zig')) -# [Critical Social Infrastructure for Zig Communities | Loris Cro's Blog](https://kristoff.it/blog/critical-social-infrastructure/) +## [Critical Social Infrastructure for Zig Communities | Loris Cro's Blog](https://kristoff.it/blog/critical-social-infrastructure/) ($section.id('zig-community-infrastructure')) 对于一个试图共同学习如何制作大家都喜欢的软件的社区来说,能够分享想法并开展合作至关重要,但社交平台的不断起伏会导致连接中断,这对于一个从一开始就希望去中心化的社区来说是个大问题。 @@ -74,25 +74,25 @@ Zig 就是这样一个项目。 - - -# [The Zig Website Has Been Re-engineered](https://ziglang.org/news/website-zine/) +## [The Zig Website Has Been Re-engineered](https://ziglang.org/news/website-zine/) ($section.id('zig-website-reengineered')) Zig 官网已经用 [Zine](https://zine-ssg.io/) 重写! -# [Rust vs. Zig in Reality: A (Somewhat) Friendly Debate](https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/) +## [Rust vs. Zig in Reality: A (Somewhat) Friendly Debate](https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/) ($section.id('rust-vs-zig-debate')) -# [Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun](https://kevinlynagh.com/rust-zig/) +## [Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun](https://kevinlynagh.com/rust-zig/) ($section.id('rust-keyboard-firmware-zig')) -# [Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig](https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html) +## [Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig](https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html) ($section.id('zig-programmers-cashing-in')) -# 视频 +## 视频 ($section.id('videos')) -## [I made an operating system that self replicates doom on a network “from scratch”](https://www.youtube.com/watch?v=xOySJpQlmv4&feature=youtu.be) +### [I made an operating system that self replicates doom on a network "from scratch"](https://www.youtube.com/watch?v=xOySJpQlmv4&feature=youtu.be) ($section.id('os-self-replicates-doom')) -## [Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature=shared&v=3fWx5BOiUiY) +### [Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature=shared&v=3fWx5BOiUiY) ($section.id('rust-zig-go-performance')) -## [Let's explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764) +### [Let's explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764) ($section.id('vulkan-api-zig')) -# 项目/工具 +## 项目/工具 ($section.id('projects-tools')) [laohanlinux/boltdb-zig](https://github.com/laohanlinux/boltdb-zig) a zig implement kv database @@ -107,7 +107,7 @@ Console Blackjack written in Zig Instructions on setting up VSCode to debug Zig on Linux [lframosferreira/brainzuck](https://github.com/lframosferreira/brainzuck) -[Brainf\*ck](https://en.wikipedia.org/wiki/Brainfuck) interpreter +[Brainf*ck](https://en.wikipedia.org/wiki/Brainfuck) interpreter written in Zig 0.12.0! Have fun! [BitlyTwiser/snek](https://github.com/BitlyTwiser/snek) @@ -131,4 +131,4 @@ Video editor 🎬 written in Zig ⚡ using raylib [pwbh/ymlz](https://github.com/pwbh/ymlz) Small and convenient yaml parser for Zig -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-10-01..2024-11-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-10-01..2024-11-01) ($section.id('zig-updates-202410')) diff --git a/content/monthly/202411.smd b/content/monthly/202411.smd index 75f5881..da8ab44 100644 --- a/content/monthly/202411.smd +++ b/content/monthly/202411.smd @@ -6,9 +6,9 @@ .draft = false, --- -# 观点/教程 +## 观点/教程 ($section.id('opinion-tutorial')) -# [Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html) +## [Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html) ($section.id('js-toolchain-in-zig')) [JAM](https://github.com/srijan-paul/jam) 作者写的一篇文章,分析里市面上现有的 JS @@ -48,7 +48,7 @@ esquery 的问题在于执行效率,通过借助于 zig 的 comptime 来在编译器翻译 esquery 就避免了运行时开销。 -# [Advent of Code in Zig | Loris Cro's Blog](https://kristoff.it/blog/advent-of-code-zig/) +## [Advent of Code in Zig | Loris Cro's Blog](https://kristoff.it/blog/advent-of-code-zig/) ($section.id('advent-of-code-zig')) 一年一度的 AoC 又到了,这篇文章里给出了一些使用的技巧来帮助大家用 Zig 来解决 AoC 问题。 @@ -99,9 +99,9 @@ > 的某些功能甚至能帮助您取得比其他语言更快的进展,但它最终还是针对软件工程进行了优化,而这并不是您在 > AoC 中要做的事情。 -# [Zig and Emulators](https://floooh.github.io/2024/08/24/zig-and-emulators.html) +## [Zig and Emulators](https://floooh.github.io/2024/08/24/zig-and-emulators.html) ($section.id('zig-and-emulators')) -# [Zig Reproduced Without Binaries](https://jakstys.lt/2024/zig-reproduced-without-binaries/) +## [Zig Reproduced Without Binaries](https://jakstys.lt/2024/zig-reproduced-without-binaries/) ($section.id('zig-reproduced-without-binaries')) 一个很有趣的实验,在 0.10 版本中,Zig 编译器实现了自举,即可以用老版本的 Zig 来编译 Zig 源码,生成最新的 Zig @@ -130,7 +130,7 @@ Zig,利用 LLVM 后端,以 wasm32-wasi 为目标生成的二进制文件。 - [Why is the bootstrapping process so complicated? : r/Zig](https://www.reddit.com/r/Zig/comments/142gwls/why_is_the_bootstrapping_process_so_complicated/) -# 项目/工具 +## 项目/工具 ($section.id('projects-tools')) [Builds (Zig) - GoReleaser](https://goreleaser.com/customization/zig-builds/) 版本发布工具 GoReleaser 支持了 Zig @@ -151,4 +151,4 @@ PDF reader for terminal emulators using the Kitty image protocol [Dr-Nekoma/lyceum](https://github.com/Dr-Nekoma/lyceum) An MMO game written in Erlang (+ PostgreSQL) + Zig (+ Raylib) -# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-11-01..2024-12-01) +## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-11-01..2024-12-01) ($section.id('zig-updates-202411')) diff --git a/content/post/0.14.smd b/content/post/0.14.smd index 9ba4469..f38b239 100644 --- a/content/post/0.14.smd +++ b/content/post/0.14.smd @@ -8,16 +8,16 @@ https://ziglang.org/download/0.14.0/release-notes.html -# 发布概览 +## [发布概览]($section.id('release-overview')) Zig 0.14.0 版本是经过 **9 个月的工作**,由 **251 位不同的贡献者** 完成,包含 **3467 个提交** 的成果。该版本专注于提升 **健壮性**、**最优性** 和 **可重用性**,并通过 Zig 软件基金会 (Zig Software Foundation) 资助开发。 -# 核心主题与重要更新 +## [核心主题与重要更新]($section.id('key-topics-and-updates')) 1. **提升编译速度与开发效率:** - 版本说明强调了两个重要的长期投资:**增量编译 (Incremental Compilation)** 和 **快速 x86 后端 (fast x86 Backend)**。 -- 这两项投资的核心目标是 **“reducing edit/compile/debug cycle latency”**(减少编辑/编译/调试循环延迟)。 +- 这两项投资的核心目标是 **"reducing edit/compile/debug cycle latency"**(减少编辑/编译/调试循环延迟)。 - **增量编译** 功能在本次发布中可以通过 -fincremental 标志选择启用,但目前尚未完全成熟。它在配合文件系统监控时表现良好,尤其是在仅检查编译错误时能显著提升反馈速度。 - 引用:$ zig build -Dno-bin -fincremental --watch (展示了在大型代码库中快速获得编译错误反馈的示例)。 - 目前不兼容 usingnamespace,建议用户尽量避免使用。 @@ -53,9 +53,9 @@ Zig 0.14.0 版本是经过 **9 个月的工作**,由 **251 位不同的贡献 - **移除 @fenceStoreLoad Barriers:** 移除 @fence(.StoreLoad),其功能现在可以通过使用 SeqCst 或 Acquire/Release 原子操作来实现。 - **Packed Struct Equality 和 Packed Struct Atomics:** 允许直接对 Packed Struct 进行相等性比较和原子操作,不再需要 @bitCast 到底层整数类型。 - **@ptrCast 允许改变切片长度 (@ptrCast Allows Changing Slice Length):** #22706 -- **移除匿名结构体类型,统一元组 (Remove Anonymous Struct Types, Unify Tuples):** 重构匿名结构体字面量和元组的工作方式,使其使用“普通”结构体类型和基于 AST 节点及结构体的等价性。 +- **移除匿名结构体类型,统一元组 (Remove Anonymous Struct Types, Unify Tuples):** 重构匿名结构体字面量和元组的工作方式,使其使用"普通"结构体类型和基于 AST 节点及结构体的等价性。 - **Calling Convention 增强和 @setAlignStack 被取代 (Calling Convention Enhancements and @setAlignStack Replaced):** std.builtin.CallingConvention 现在是一个标记联合,包含更多目标平台特定的调用约定,并允许通过 CommonOptions 设置栈对齐等选项。.c 调用约定现在是一个声明,可以通过 callconv(.c) 使用 Decl Literals 访问。@setAlignStack 被移除,其功能现在通过调用约定的选项实现。 -- \*_std.builtin.Type 字段重命名和简化 (std.builtin.Type Fields Renamed and Simplify Usage Of ?const anyopaque):_ std.builtin.Type 联合体的字段名称改为小写,并增加了对 default\_value\_ptr 和 sentinel\_ptr 字段的 helper 方法,以简化使用。 +- *_std.builtin.Type 字段重命名和简化 (std.builtin.Type Fields Renamed and Simplify Usage Of ?const anyopaque):_ std.builtin.Type 联合体的字段名称改为小写,并增加了对 default\_value\_ptr 和 sentinel\_ptr 字段的 helper 方法,以简化使用。 - **不允许非标量哨兵类型 (Non-Scalar Sentinel Types Disallowed):** 哨兵值现在只能是支持 == 操作符的标量类型。 - **@FieldType 内置函数 (@FieldType builtin):** 新增 @FieldType 内置函数,用于获取给定类型和字段名称的字段类型,取代了 std.meta.FieldType 函数。 - **@src 获得 Module 字段 (@src Gains Module Field):** std.builtin.SourceLocation 结构体新增 module 字段。 @@ -74,7 +74,7 @@ Zig 0.14.0 版本是经过 **9 个月的工作**,由 **251 位不同的贡献 - **Transport Layer Security (std.crypto.tls):** #21872 - **process.Child.collectOutput API 变化 (#21872process.Child.collectOutput API Changed):** API 签名改变,现在将 allocator 作为第一个参数传入。 - **LLVM Builder API:** LLVM bitcode builder API 移至 std.zig.llvm,方便第三方项目复用。 -- **拥抱“非管理”风格容器 (Embracing "Unmanaged"-Style Containers):** 大部分带有内置 allocator 的标准库容器(如 std.ArrayList, std.ArrayHashMap)已被弃用,推荐使用“非管理”风格容器(如 std.ArrayListUnmanaged, std.ArrayHashMapUnmanaged),并在需要时显式传递 allocator。 +- **拥抱"非管理"风格容器 (Embracing "Unmanaged"-Style Containers):** 大部分带有内置 allocator 的标准库容器(如 std.ArrayList, std.ArrayHashMap)已被弃用,推荐使用"非管理"风格容器(如 std.ArrayListUnmanaged, std.ArrayHashMapUnmanaged),并在需要时显式传递 allocator。 - **std.c 重组 (std.c Reorganization):** 重组了 std.c,使其结构更清晰,并改变了对不存在符号的处理方式(从 @compileError 改为 void 或 {}),移除了标准库中最后一个 usingnamespace 的使用点。 - **弃用列表 (List of Deprecations):** 列出了大量被弃用或重命名的标准库函数和类型。 - **Binary Search:** #20927 @@ -106,7 +106,7 @@ Zig 0.14.0 版本是经过 **9 个月的工作**,由 **251 位不同的贡献 8. **Bug 修复与 Toolchain 更新 (Bug Fixes and Toolchain):** -- 关闭了 416 个 bug 报告。但版本说明坦承 **“This Release Contains Bugs”**,并指出在 1.0.0 版本达到 Tier 1 支持时会增加 bug 策略。 +- 关闭了 416 个 bug 报告。但版本说明坦承 **"This Release Contains Bugs"**,并指出在 1.0.0 版本达到 Tier 1 支持时会增加 bug 策略。 - **UBSan Runtime:** Debug 模式下默认启用 UBSan 运行时库,为 C 代码的未定义行为提供更详细的恐慌信息和堆栈跟踪。可以通过 -fno-ubsan-rt 和 -fubsan-rt 控制。 - **compiler\_rt:** 包含了优化的 memcpy 实现。 - **musl 1.2.5:** 捆绑的 musl 更新并应用了 CVE 修复和目标平台特定补丁。不再捆绑 musl 的 memcpy 文件,而是使用 Zig 的优化实现。 @@ -124,13 +124,13 @@ Zig 0.14.0 版本是经过 **9 个月的工作**,由 **251 位不同的贡献 - 最终目标是达到 1.0.0 版本,届时 Tier 1 支持将包含 bug 政策。 -# 关于 Zig 0.14.0 版本的常见问题解答 +## [关于 Zig 0.14.0 版本的常见问题解答]($section.id('faq-zig-0-14-0')) -# Zig 0.14.0 版本的主要更新和亮点是什么? +## [Zig 0.14.0 版本的主要更新和亮点是什么?]($section.id('main-updates-and-highlights')) Zig 0.14.0 版本是长达 9 个月开发工作和 3467 次提交的成果,主要亮点包括:显著增强了对多种目标平台的支持,包括 arm/thumb、mips/mips64、powerpc/powerpc64、riscv32/riscv64 和 s390x 等,许多之前存在工具链问题、标准库支持缺失或崩溃的情况现在应该可以正常工作了。此外,该版本在构建系统方面进行了大量升级,并对语言进行了多项重要改进,例如引入了 Labeled Switch 和 Decl Literals 等新特性。为了缩短编辑/编译/调试周期,版本还迈向了两个长期投资目标:增量编译和快速 x86 后端。 -# Zig 如何对不同目标平台的开发支持进行分级? +## [Zig 如何对不同目标平台的开发支持进行分级?]($section.id('target-support-tiers')) Zig 使用四层系统来对不同目标平台的支持级别进行分类,其中 Tier 1 是最高级别: @@ -139,15 +139,15 @@ Zig 使用四层系统来对不同目标平台的支持级别进行分类,其 - **Tier 3:** 编译器可以依赖外部后端(如 LLVM)为该目标平台生成机器代码。链接器可以为该目标平台生成目标文件、库和可执行文件。 - **Tier 4:** 编译器可以依赖外部后端(如 LLVM)为该目标平台生成汇编源代码。如果 LLVM 将此目标平台视为实验性,则需要从源代码构建 LLVM 和 Zig 才能使用它。 -# 什么是 Labeled Switch,它有什么优势? +## [什么是 Labeled Switch,它有什么优势?]($section.id('labeled-switch-advantages')) Labeled Switch 是 Zig 0.14.0 中引入的一项语言特性,允许 switch 语句被标记,并作为 continue 语句的目标。continue :label value 语句会用 value 替换原始的 switch 表达式操作数,并重新评估 switch。尽管在语义上类似于循环中的 switch,但 Labeled Switch 的关键优势在于其代码生成特性。它可以生成帮助 CPU 更准确预测分支的代码,从而提高热循环中的性能,特别是在处理指令分派、评估有限状态自动机 (FSA) 或执行类似基于 case 的评估时。这有助于 branch predictor 更准确地预测控制流。 -# Decl Literals 是什么,它解决了哪些问题? +## [Decl Literals 是什么,它解决了哪些问题?]($section.id('decl-literals-explanation')) Decl Literals 是 Zig 0.14.0 扩展 "enum literal" 语法 (.foo) 而引入的新特性。现在,一个枚举字面量 .foo 不仅可以引用枚举变体,还可以使用 Result Location Semantics 引用目标类型上的任何声明(const/var/fn)。这在初始化结构体字段时特别有用,可以避免重复指定类型,并有助于避免 Faulty Default Field Values 的问题,确保数据不变量不会因覆盖单个字段而受到破坏。它也支持直接调用函数来初始化值。 -# Zig 0.14.0 版本在内存分配器方面有哪些值得关注的变化? +## [Zig 0.14.0 版本在内存分配器方面有哪些值得关注的变化?]($section.id('allocator-changes')) 该版本对内存分配器进行了多项改进: @@ -156,7 +156,7 @@ Decl Literals 是 Zig 0.14.0 扩展 "enum literal" 语法 (.foo) 而引入的新 - **Allocator API Changes (remap):** std.mem.Allocator.VTable 引入了一个新的 remap 函数,允许尝试扩展或收缩内存并可能重新定位,如果无法在不执行内部 memcpy 的情况下完成,则返回 null,提示调用者自行处理复制。同时,resize 函数保持不变。Allocator.VTable 中的所有函数现在使用 std.mem.Alignment 类型代替 u8,增加了类型安全。 - **Runtime Page Size:** 移除了编译时已知的 std.mem.page\_size,代之以编译时已知的页面大小上下界 std.heap.page\_size\_min 和 std.heap.page\_size\_max。运行时获取页面大小可以使用 std.heap.pageSize(),它会优先使用编译时已知的值,否则在运行时查询操作系统并缓存结果。这修复了对 Asahi Linux 等新硬件上运行 Linux 的支持。 -# Zig 0.14.0 版本如何改进构建系统,特别是处理模块和依赖关系? +## [Zig 0.14.0 版本如何改进构建系统,特别是处理模块和依赖关系?]($section.id('build-system-improvements')) Zig 0.14.0 版本在构建系统方面有多项重要改进: @@ -166,7 +166,7 @@ Zig 0.14.0 版本在构建系统方面有多项重要改进: - **addLibrary Function:** 引入 addLibrary 函数作为 addSharedLibrary 和 addStaticLibrary 的替代,允许在 build.zig 中更容易地切换链接模式,并与 linkLibrary 函数名称保持一致。 - **Import ZON:** ZON 文件现在可以在编译时通过 @import("foo.zon") 导入,前提是结果类型已知。 -# Zig 0.14.0 版本在编译器后端和编译速度方面有哪些进展? +## [Zig 0.14.0 版本在编译器后端和编译速度方面有哪些进展?]($section.id('compiler-backend-and-speed')) 该版本在编译器后端和编译速度方面取得了进展: @@ -174,7 +174,7 @@ Zig 0.14.0 版本在构建系统方面有多项重要改进: - **Incremental Compilation:** 引入了增量编译特性,可以通过 -fincremental 标志启用。尽管尚未默认启用,但结合文件系统监听,可以显著缩短修改代码后的重新分析时间,提供快速的编译错误反馈。 - **x86 Backend:** x86 后端在行为测试套件中的通过率已接近 LLVM 后端,并且在开发时通常比 LLVM 后端提供更快的编译速度和更好的调试器支持。虽然尚未默认选中,但鼓励用户尝试使用 -fno-llvm 或在构建脚本中设置 use\_llvm = false 来启用。 -# Zig 0.14.0 版本在工具链和运行时方面有哪些值得注意的更新? +## [Zig 0.14.0 版本在工具链和运行时方面有哪些值得注意的更新?]($section.id('toolchain-and-runtime-updates')) 该版本在工具链和运行时方面也有多项更新: @@ -189,6 +189,6 @@ Zig 0.14.0 版本在构建系统方面有多项重要改进: - **Optimized memcpy:** 提供了优化的 memcpy 实现,不再捆绑 musl 的 memcpy 文件。 - **Integrated Fuzzer:** 集成了 alpha 质量的 fuzzer,可以通过 --fuzz CLI 选项使用,并提供一个 fuzzer Web UI 显示实时代码覆盖率。 -# 总结 +## [总结]($section.id('conclusion')) Zig 0.14.0 版本是向 1.0.0 版本迈进的重要一步,在性能优化(尤其是编译速度)、跨平台支持、语言特性和标准库方面都带来了显著改进。增量编译和快速 x86 后端是关键的长期投资,旨在提升开发者体验。新语言特性如 Labeled Switch 和 Decl Literals 提供了更强大和安全的编程模式。标准库的重组和容器的调整反映了社区的使用模式和最佳实践。构建系统也获得了重要升级,使模块管理和依赖处理更加灵活。尽管仍存在已知 bug,但 Zig 社区在本次发布中展示了活跃的开发和持续的进步。 diff --git a/content/post/2023-09-05-bog-gc-1-en.smd b/content/post/2023-09-05-bog-gc-1-en.smd index 19b8056..b5cced9 100644 --- a/content/post/2023-09-05-bog-gc-1-en.smd +++ b/content/post/2023-09-05-bog-gc-1-en.smd @@ -10,11 +10,11 @@ }, --- -# Bog GC Design +## [Bog GC Design]($section.id('bog-gc-design')) Bog is a small scripting language developed using Zig. Its GC design is inspired by a paper titled [An efficient of Non-Moving GC for Function languages](https://www.pllab.riec.tohoku.ac.jp/papers/icfp2011UenoOhoriOtomoAuthorVersion.pdf). -# Overview +## [Overview]($section.id('bog-gc-design')) 1. Introduction - Design of the Heap @@ -22,7 +22,7 @@ Bog is a small scripting language developed using Zig. Its GC design is inspired - Design of Bitmap 2. Implementation -# Introduction +## [Introduction]($section.id('bog-gc-design')) GC stands for garbage collection, which is primarily a memory management strategy for the `heap` region. Memory allocations in the heap are done in exponentially increasing sizes, with a special sub-heap dedicated solely to very large objects. One advantage of this approach might be its efficiency in handling memory requests of various sizes. @@ -71,13 +71,13 @@ graph TD +------------+ ``` -## Memory Sub-Heap Pool +### [Memory Sub-Heap Pool]($section.id('bog-gc-design')) We've designed a memory resource pool. This pool consists of numerous allocation segments of fixed size. It means that, regardless of how much space a sub-heap requests, it will request it in units of these fixed-size "segments". For instance, if the allocation segments in the pool are of size 1MB, then a sub-heap might request space in sizes of 1MB, 2MB, 3MB, etc., rather than requesting non-integer multiples like 1.5MB or 2.5MB. The dynamic allocation and reclaiming of space by the sub-heaps from a resource pool made up of fixed-size segments provide greater flexibility and may enhance the efficiency of memory utilization. -## Types of GC +### [Types of GC]($section.id('bog-gc-design')) There are many common types of GC. @@ -136,7 +136,7 @@ However, Non-Moving GC has its disadvantages: To address these problems requires many complicated steps, which won't be elaborated on here. We'll focus on Bog's GC for the explanation. -## Meta Bitmap +### [Meta Bitmap]($section.id('bog-gc-design')) "Meta Bitmap" or "meta-level bitmaps". This is a higher-level bitmap that summarizes the contents of the original bitmap. This hierarchical structure is similar to the inode mapping in file systems or the use of multi-level page tables in computer memory management. @@ -150,11 +150,11 @@ Let's design for a 32-bit architecture. A 32-bit architecture means that the com For example, if a bitmap is 320 bits long, then on a 32-bit architecture, the worst-case scenario might require checking 10 blocks of 32 bits to find a free bit. This can be represented by log32(320), which results in 10. -## Bitmap +### [Bitmap]($section.id('bog-gc-design')) Since Bog's GC is essentially still based on "Mark-Sweep", using bitmaps to record data is indispensable. In Bog, we adopted the method of "bitmap records data" for GC. And to improve efficiency, we introduced the concept of meta-bitmaps, where every 4 elements correspond to a meta-bitmap, recording the occupancy status of multiple spaces, and increasing the depth based on the object age in the heap. -## Implementation +### [Implementation]($section.id('bog-gc-design')) In reality, Bog's design is a bit more complex. Here are sample in practical code: diff --git a/content/post/2023-09-05-bog-gc-1.smd b/content/post/2023-09-05-bog-gc-1.smd index 8e1748d..58f5731 100644 --- a/content/post/2023-09-05-bog-gc-1.smd +++ b/content/post/2023-09-05-bog-gc-1.smd @@ -10,19 +10,11 @@ }, --- -# Bog GC 设计 -- 概念篇 +## [Bog GC Design -- Concepts]($section.id('bog-gc-design-concepts')) [Bog](https://github.com/Vexu/bog) 是一款基于 Zig 开发的小型脚本语言。它的 GC 设计受到一篇论文[An efficient of Non-Moving GC for Function languages](https://www.pllab.riec.tohoku.ac.jp/papers/icfp2011UenoOhoriOtomoAuthorVersion.pdf)的启发。 -# 梗概 - -1. 概述 - - Heap 的设计 - - GC 的类别 - - Bitmap 的设计 -2. 实现 - -# 概述 +## [Summary]($section.id('summary')) GC 是一种垃圾回收的机制,主要是针对`heap`区域的内存管理策略。在堆中的内存分配是按照指数级增长的大小进行的,此外还有一个专门用于非常大对象的特殊子堆。这种方法的一个优点可能是它可以高效地处理各种大小的内存请求。 @@ -71,17 +63,17 @@ graph TD +------------+ ``` -## 内存子堆池 +### [Memory Sub-Heap Pool]($section.id('memory-sub-heap-pool')) -我们设计一个内存的资源池。这个池由许多固定大小的分配段组成。这意味着,无论子堆请求多少空间,它都会以这些固定大小的“段”为单位来请求。例如,如果池中的分配段大小为 1MB,那么一个子堆可能会请求 1MB、2MB、3MB 等大小的空间,而不是请求 1.5MB 或 2.5MB 这样的非整数倍的大小。 +我们设计一个内存的资源池。这个池由许多固定大小的分配段组成。这意味着,无论子堆请求多少空间,它都会以这些固定大小的"段"为单位来请求。例如,如果池中的分配段大小为 1MB,那么一个子堆可能会请求 1MB、2MB、3MB 等大小的空间,而不是请求 1.5MB 或 2.5MB 这样的非整数倍的大小。 而子堆从一个由固定大小的段组成的资源池中动态分配和回收空间,这种策略可以提供更高的灵活性,并可能提高内存使用的效率。 -## GC 的类别 +### [Types of GC]($section.id('types-of-gc')) 常见的 GC 有很多类型。 -以**位图记录数据**来说,有“代际垃圾收集 GC”。在代际垃圾收集(Generational Garbage Collection)中,"代"或"世代"并不是指位图。它们实际上是指内存中的一部分,用于存储对象。基于对象的生存周期,GC 把它们分为不同的代。这种策略背后的基本思想是,新创建的对象很快就会变为垃圾,而旧对象则可能存活得更久。 +以**位图记录数据**来说,有"代际垃圾收集 GC"。在代际垃圾收集(Generational Garbage Collection)中,"代"或"世代"并不是指位图。它们实际上是指内存中的一部分,用于存储对象。基于对象的生存周期,GC 把它们分为不同的代。这种策略背后的基本思想是,新创建的对象很快就会变为垃圾,而旧对象则可能存活得更久。 一般来说,在代际 GC 中,有两个主要的代: @@ -91,22 +83,22 @@ graph TD 位图在这里是一个工具,用于跟踪和管理每一代中哪些对象是活跃的(即仍在使用中)和哪些是垃圾。当算法扩展为代际 GC 时,可以为同一个堆空间的不同代维护多个位图。这样,每个代的活动和非活动对象都可以被单独地跟踪。 -“为新生代维护一个位图,为老年代维护另一个位图”。这使得在进行垃圾收集时,我们可以单独地考虑每一个代,从而优化垃圾收集的效率和性能。 +"为新生代维护一个位图,为老年代维护另一个位图"。这使得在进行垃圾收集时,我们可以单独地考虑每一个代,从而优化垃圾收集的效率和性能。 -以是否**移动旧有的数据**来说,有“移动 GC”和“非移动 GC”。移动 GC 会将存活的对象移动到新的内存地址、压缩内存,而非移动 GC 则不会移动存活的对象,可以无缝由其他语言来调用内存里的数据。 +以是否**移动旧有的数据**来说,有"移动 GC"和"非移动 GC"。移动 GC 会将存活的对象移动到新的内存地址、压缩内存,而非移动 GC 则不会移动存活的对象,可以无缝由其他语言来调用内存里的数据。 -在这种移动 GC 中,有“分代复制收集器”和“Cheney 复制收集器”。 +在这种移动 GC 中,有"分代复制收集器"和"Cheney 复制收集器"。 -分代复制收集器(Generational Copying Collector): 是垃圾收集的一种常见方法,特别是在函数式编程语言中。它假设新创建的对象很快就会变得不可达(即“死亡”),而老的对象则更可能持续存在。因此,内存被分成两个或更多的“代”,新对象在“新生代”中创建,当它们存活足够长的时间时,它们会被移到“老生代”。 +分代复制收集器(Generational Copying Collector): 是垃圾收集的一种常见方法,特别是在函数式编程语言中。它假设新创建的对象很快就会变得不可达(即"死亡"),而老的对象则更可能持续存在。因此,内存被分成两个或更多的"代",新对象在"新生代"中创建,当它们存活足够长的时间时,它们会被移到"老生代"。 -Cheney 的拷贝收集器: 是一种用于半区(semi-space)的拷贝垃圾收集器。它的工作原理是将可用内存分为两半,并且只在其中一半中分配对象。当这一半用完时,收集器通过拷贝活跃对象到另一半空间来进行垃圾收集。然后,原先的那一半空间就完全清空,成为新的可用空间。Cheney 的收集器特别适用于处理短生命周期的数据,因为它可以快速地只拷贝活跃的数据,而忽略死亡的数据。这使得它在处理大量短生命周期数据的程序(例如函数式程序)时,对于其“次要收集”(minor collection,即仅仅回收新生代数据的收集)非常高效。**这种方法的优势在于它可以有效地处理内存碎片,因为通过复制活动对象到新位置,内存会被连续地占用。** +Cheney 的拷贝收集器: 是一种用于半区(semi-space)的拷贝垃圾收集器。它的工作原理是将可用内存分为两半,并且只在其中一半中分配对象。当这一半用完时,收集器通过拷贝活跃对象到另一半空间来进行垃圾收集。然后,原先的那一半空间就完全清空,成为新的可用空间。Cheney 的收集器特别适用于处理短生命周期的数据,因为它可以快速地只拷贝活跃的数据,而忽略死亡的数据。这使得它在处理大量短生命周期数据的程序(例如函数式程序)时,对于其"次要收集"(minor collection,即仅仅回收新生代数据的收集)非常高效。**这种方法的优势在于它可以有效地处理内存碎片,因为通过复制活动对象到新位置,内存会被连续地占用。** 特点: - **任何精确的 copy gc 都需要 runtime 系统定位和更新每个堆分配数据的所有指针** - 在传统的垃圾收集策略(移动 GC)中,压缩是一种常用的技术,它会将活跃的对象移到内存的一个连续区域中,从而释放出未使用的内存。换言之,就是整理内存碎片。 -在非移动 GC 中,有“标记-清除”。 +在非移动 GC 中,有"标记-清除"。 **不需要进行压缩和对象移动这一特性是非常重要**,指针的值(即对象的内存地址)是固定的,也不需要花时间更新移动的地址。这使得非移动 GC 非常适合于需要与其他语言进行交互的语言,因为它们可以在不需要额外的工作的情况下访问内存中的对象。而且不需要移动对象的这一特性对于支持多原生线程也是有益的。在多线程环境中,如果对象在内存中的位置不断移动,那么线程之间的协调和同步将变得更加复杂。因此,避免对象移动可以简化多线程编程。 @@ -131,9 +123,9 @@ Cheney 的拷贝收集器: 是一种用于半区(semi-space)的拷贝垃圾 为了解决这些问题需要很多复杂的步骤,在此不多赘述。单以 Bog 的 GC 来讲解。 -## Meta Bitmap +### [Meta Bitmap]($section.id('meta-bitmap')) -“元级位图”或“meta-level bitmaps”。这是一个更高级别的位图,用于汇总原始位图的内容。这种层次化的结构类似于文件系统中的 inode 映射或多级页表在计算机内存管理中的使用。 +"元级位图"或"meta-level bitmaps"。这是一个更高级别的位图,用于汇总原始位图的内容。这种层次化的结构类似于文件系统中的 inode 映射或多级页表在计算机内存管理中的使用。 例如,考虑一个简单的位图:`1100 1100`。一个元级位图可能表示每 4 位中有多少个空闲块。在这种情况下,元级位图可能是 `1021`(表示第一个 4 位中有 1 个空闲块,第二个 4 位中有 2 个空闲块,以此类推)。 @@ -145,11 +137,11 @@ Cheney 的拷贝收集器: 是一种用于半区(semi-space)的拷贝垃圾 例如,如果一个位图有 320 位,那么在 32 位架构上,最坏的情况可能需要检查 10 个 32 位块才能找到一个空闲位。这可以通过 $\log_{32}(320)$来表示,结果是 10。 -## Bitmap +### [Bitmap]($section.id('bitmap')) -由于 Bog 的 GC 本质上还是采用了“标记-清除”,所以利用位图来记录数据是必不可少的。在 Bog 中,我们采用了“位图记录数据”的方式来进行 GC。而为了提高效率,我们增加了元位图的概念,即每 4 个元素对应一个元位图,用于记录多空间的占用状态,并且根据 heap 的对象时间增加深度。 +由于 Bog 的 GC 本质上还是采用了"标记-清除",所以利用位图来记录数据是必不可少的。在 Bog 中,我们采用了"位图记录数据"的方式来进行 GC。而为了提高效率,我们增加了元位图的概念,即每 4 个元素对应一个元位图,用于记录多空间的占用状态,并且根据 heap 的对象时间增加深度。 -## 实现 +### [Implementation]($section.id('implementation')) 实际上,在 Bog 的设计中,要更加复杂一些。我们增加了 diff --git a/content/post/2023-09-05-hello-world.smd b/content/post/2023-09-05-hello-world.smd index 3a4b8f1..42de01e 100644 --- a/content/post/2023-09-05-hello-world.smd +++ b/content/post/2023-09-05-hello-world.smd @@ -11,7 +11,7 @@ - [ZigCC 网站](https://ziglang.cc) - [ZigCC 公众号](https://github.com/zigcc/.github/raw/main/zig_mp.png) -# 供稿方式 +## 供稿方式 TODO @@ -27,6 +27,6 @@ date: '2023-09-05T16:13:13+0800' --- ``` -# 本地预览 +## 本地预览 在写完文章后,可以使用 [Hugo](https://gohugo.io/) 进行本地预览,只需在项目根目录执行 `hugo server`,这会启动一个 HTTP 服务,默认的访问地址是: http://localhost:1313/ diff --git a/content/post/2023-09-21-zig-midi.smd b/content/post/2023-09-21-zig-midi.smd index 3e91fbf..47d03e4 100644 --- a/content/post/2023-09-21-zig-midi.smd +++ b/content/post/2023-09-21-zig-midi.smd @@ -6,7 +6,7 @@ .draft = false, --- -MIDI 是“乐器数字接口”的缩写,是一种用于音乐设备之间通信的协议。而 [zig-midi](https://github.com/Hejsil/zig-midi) 主要是在对 MIDI 的元数据、音频头等元数据进行一些处理的方法上进行了集成。 +MIDI 是"乐器数字接口"的缩写,是一种用于音乐设备之间通信的协议。而 [zig-midi](https://github.com/Hejsil/zig-midi) 主要是在对 MIDI 的元数据、音频头等元数据进行一些处理的方法上进行了集成。 ```bash . @@ -23,11 +23,11 @@ MIDI 是“乐器数字接口”的缩写,是一种用于音乐设备之间通 ├── midi.zig ``` -# 基础 +## [基础]($section.id('basic')) 在 MIDI 协议中,`0xFF` 是一个特定的状态字节,用来表示元事件(Meta Event)的开始。元事件是 MIDI 文件结构中的一种特定消息,通常不用于实时音频播放,但它们包含有关 MIDI 序列的元数据,例如序列名称、版权信息、歌词、时间标记、速度(BPM)更改等。 -以下是一些常见的元事件类型及其关联的 `0xFF` 后的字节: +以下是一些常见的元事件类型及其关联的 `0xFF`后的字节: - `0x00`: 序列号 (Sequence Number) - `0x01`: 文本事件 (Text Event) @@ -56,7 +56,7 @@ MIDI 是“乐器数字接口”的缩写,是一种用于音乐设备之间通 元事件主要存在于 MIDI 文件中,特别是在标准 MIDI 文件 (SMF) 的上下文中。在实时 MIDI 通信中,元事件通常不会被发送,因为它们通常不会影响音乐的实际播放。 -# Midi.zig +## Midi.zig 本文件主要是处理 MIDI 消息的模块,为处理 MIDI 消息提供了基础结构和函数。 @@ -194,7 +194,7 @@ pub const Message = struct { - value 和 setValue 函数:用于获取和设置 MIDI 消息的值字段。 - Kind 枚举:定义了 MIDI 消息的所有可能种类,包括通道事件和系统事件。 -## midi 消息结构 +### midi 消息结构 我们需要先了解 MIDI 消息的一些背景。 @@ -211,7 +211,7 @@ pub const Message = struct { 4. **设置值**:`message.values = .{ ... }` 将这两个 7 位的值设置到 `message.values` 中。 -## 事件 +### 事件 针对事件,我们看 enum。 @@ -251,7 +251,7 @@ pub const Message = struct { 以下是对每个枚举成员的简要说明: -### 频道事件 (Channel events) +#### 频道事件 (Channel events) 1. **NoteOff**:这是一个音符结束事件,表示某个音符不再播放。 2. **NoteOn**:这是一个音符开始事件,表示开始播放某个音符。 @@ -261,7 +261,7 @@ pub const Message = struct { 6. **ChannelPressure**:频道压力事件,与多声道键盘压力相似,但它适用于整个频道,而不是特定音符。 7. **PitchBendChange**:音高弯曲变更事件,表示音符音高的上升或下降。 -### 系统事件 (System events) +#### 系统事件 (System events) 1. **ExclusiveStart**:独占开始事件,标志着一个独占消息序列的开始。 2. **MidiTimeCodeQuarterFrame**:MIDI 时间码四分之一帧,用于同步与其他设备。 @@ -276,11 +276,11 @@ pub const Message = struct { 11. **ActiveSensing**:活动感知事件,是一种心跳信号,表示设备仍然在线并工作。 12. **Reset**:重置事件,用于将设备重置为其初始状态。 -### 其他 +#### 其他 1. **Undefined**:未定义事件,可能表示一个未在此枚举中定义的或无效的 MIDI 事件。 -# decode.zig +## decode.zig 本文件是对 MIDI 文件的解码器, 提供了一组工具,可以从不同的输入源解析 MIDI 文件的各个部分。这样可以方便地读取和处理 MIDI 文件。 @@ -488,7 +488,7 @@ pub fn file(reader: anytype, allocator: *mem.Allocator) !midi.File { } ``` -1. statusByte: 解析 MIDI 消息的首个字节,来确定是否这是一个状态字节,还是一个数据字节。将一个字节 b 解码为一个 u7 类型的 MIDI 状态字节,如果字节 b 不是一个状态字节,则返回 null。换句话说,midi 的消息是 14 位,如果高 7 位不为空,则是 midi 消息的状态字节。在 MIDI 协议中,消息的首个字节通常是状态字节,但也可能用之前的状态字节(这称为“运行状态”)来解释接下来的字节。因此,这段代码需要确定它是否读取了一个新的状态字节,或者它是否应该使用前一个消息的状态字节。 +1. statusByte: 解析 MIDI 消息的首个字节,来确定是否这是一个状态字节,还是一个数据字节。将一个字节 b 解码为一个 u7 类型的 MIDI 状态字节,如果字节 b 不是一个状态字节,则返回 null。换句话说,midi 的消息是 14 位,如果高 7 位不为空,则是 midi 消息的状态字节。在 MIDI 协议中,消息的首个字节通常是状态字节,但也可能用之前的状态字节(这称为"运行状态")来解释接下来的字节。因此,这段代码需要确定它是否读取了一个新的状态字节,或者它是否应该使用前一个消息的状态字节。 2. readDataByte: 从 reader 中读取并返回一个数据字节。如果读取的字节不符合数据字节的规定,则抛出 InvalidDataByte 错误。 3. message: 从 reader 读取并解码一个 MIDI 消息。如果读取的字节不能形成一个有效的 MIDI 消息,则抛出 InvalidMessage 错误。这是一个复杂的函数,涉及到解析 MIDI 消息的不同种类。 4. chunk,chunkFromBytes: 这两个函数从 reader 或直接从字节数组 bytes 中解析一个 MIDI 文件块头。 @@ -498,7 +498,7 @@ pub fn file(reader: anytype, allocator: *mem.Allocator) !midi.File { 8. trackEvent: 从 reader 中解析一个 MIDI 轨道事件。它可以是 MIDI 消息或元事件。 9. file: 用于从 reader 解码一个完整的 MIDI 文件。它首先解码文件头,然后解码所有的文件块。这个函数会返回一个表示 MIDI 文件的结构体。 -## message 解析 +### message 解析 ```zig const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { @@ -512,7 +512,7 @@ const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { } else return error.InvalidMessage; ``` -这段代码的目的是确定 MIDI 消息的状态字节。它可以是从 `reader` 读取的当前字节,或者是从前一个 MIDI 消息中获取的。这样做是为了支持 MIDI 协议中的“运行状态”,在该协议中,连续的 MIDI 消息可能不会重复状态字节。 +这段代码的目的是确定 MIDI 消息的状态字节。它可以是从 `reader` 读取的当前字节,或者是从前一个 MIDI 消息中获取的。这样做是为了支持 MIDI 协议中的"运行状态",在该协议中,连续的 MIDI 消息可能不会重复状态字节。 1. `const status_byte = ...;`: 这是一个常量声明。`status_byte` 将保存 MIDI 消息的状态字节。 @@ -531,7 +531,7 @@ const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { 4. `else return error.InvalidMessage;`: 如果 `first_byte` 不是状态字节,并且不存在前一个消息,那么返回一个 `InvalidMessage` 错误。 -# encode.zig +## encode.zig 本文件用于将 MIDI 数据结构编码为其对应的二进制形式。具体来说,它是将内存中的 MIDI 数据结构转换为 MIDI 文件格式的二进制数据。 @@ -672,7 +672,7 @@ pub fn file(writer: anytype, f: midi.File) !void { - file 函数:这是主函数,用于将整个 MIDI 文件数据结构编码为其二进制形式。它首先编码文件头,然后循环编码每个块和块中的事件。 -## int 函数 +### int 函数 ```zig @@ -718,7 +718,7 @@ pub fn int(writer: anytype, i: u28) !void { - `@intFromBool(!is_first)`: 这是将上一步得到的布尔值转换为整数。在许多编程语言中,true 通常被视为 1,false 被视为 0。在 Zig 中,这种转换不是隐式的,所以需要用`@intFromBool()`函数来进行转换 - `@as(u8, 1 << 7)`: 这里是将数字 128(从 1 << 7 得到)显式地转换为一个 8 位无符号整数。 - `(@as(u8, 1 << 7) * @intFromBool(!is_first))`: 将转换后的数字 128 与从布尔转换得到的整数(0 或 1)相乘。如果`is_first`为`true`(即这是第一个字节),那么整个表达式的值为 0。如果`is_first为false`(即这不是第一个字节),那么整个表达式的值为 128(`1000 0000` in 二进制)。 - - 这种结构在 MIDI 变长值的编码中很常见。MIDI 变长值的每个字节的最高位被用作“继续”位,指示是否有更多的字节跟随。如果最高位是 1,那么表示还有更多的字节;如果是 0,表示这是最后一个字节。 + - 这种结构在 MIDI 变长值的编码中很常见。MIDI 变长值的每个字节的最高位被用作"继续"位,指示是否有更多的字节跟随。如果最高位是 1,那么表示还有更多的字节;如果是 0,表示这是最后一个字节。 - 在每次迭代中,它提取`tmp`的最后 7 位并将其编码为一个字节,最高位根据是否是第一个字节来设置(如果是第一个字节,则为 0,否则为 1)。 - 然后,整数右移 7 位,以处理下一个字节。 - 请注意,这种编码方式实际上是从低字节到高字节的反向方式,所以接下来需要翻转这些字节。 @@ -731,7 +731,7 @@ pub fn int(writer: anytype, i: u28) !void { - 使用提供的`writer`将翻转后的字节写入到目标位置。 -# file.zig +## file.zig 主要目的是为了表示和处理 MIDI 文件的不同部分,以及提供了一个迭代器来遍历 MIDI 轨道的事件。 @@ -912,7 +912,7 @@ pub const TrackIterator = struct { - 定义了一个 Result 结构来返回事件和关联的数据。 - 提供了一个`next`方法来读取下一个事件。 -# Build.zig +## Build.zig buid.zig 是一个 Zig 构建脚本(build.zig),用于配置和驱动 Zig 的构建过程。 @@ -962,7 +962,7 @@ pub fn build(b: *Builder) void { const test_all_step = b.step("test", "Run all tests in all modes."); ``` -- 使用 b.step()方法定义了一个名为 test 的步骤。描述是“在所有模式下运行所有测试”。 +- 使用 b.step()方法定义了一个名为 test 的步骤。描述是"在所有模式下运行所有测试"。 ```zig inline for (@typeInfo(std.builtin.Mode).Enum.fields) |field| {} diff --git a/content/post/2023-12-24-zig-build-explained-part1.smd b/content/post/2023-12-24-zig-build-explained-part1.smd index 72a68f1..a47c646 100644 --- a/content/post/2023-12-24-zig-build-explained-part1.smd +++ b/content/post/2023-12-24-zig-build-explained-part1.smd @@ -15,11 +15,11 @@ Zig 构建系统仍然缺少文档,对很多人来说,这是不使用它的 我们将从一个刚刚初始化的 Zig 项目开始,逐步深入到更复杂的项目。在此过程中,我们将学习如何使用库和软件包、添加 C 代码,甚至如何创建自己的构建步骤。 -# 免责声明 +## 免责声明 由于我不会解释 Zig 语言的语法或语义,因此我希望你至少已经有了一些使用 Zig 的基本经验。我还将链接到标准库源代码中的几个要点,以便您了解所有这些内容的来源。我建议你阅读编译系统的源代码,因为如果你开始挖掘编译脚本中的函数,大部分内容都不言自明。所有功能都是在标准库中实现的,不存在隐藏的构建魔法。 -# 开始 +## 开始 我们通过新建一个文件夹来创建一个新项目,并在该文件夹中调用 zig init-exe。 @@ -56,7 +56,7 @@ pub fn build(b: *std.Build) void { } ``` -# 基础知识 +## 基础知识 构建系统的核心理念是,Zig 工具链将编译一个 Zig 程序 (build.zig),该程序将导出一个特殊的入口点(`pub fn build(b: *std.build.Builder) void`),当我们调用 `zig build` 时,该入口点将被调用。 @@ -95,7 +95,7 @@ Step 遵循与 std.mem.Allocator 相同的接口模式,需要实现一个 make 现在,我们需要创建一个稍正式的 Zig 程序: -# 编译 Zig 源代码 +## 编译 Zig 源代码 要使用编译系统编译可执行文件,编译器需要使用函数 Builder.addExecutable,它将为我们创建一个新的 LibExeObjStep。这个步骤实现是 zig build-exe、zig build-lib、zig build-obj 或 zig test 的便捷封装,具体取决于初始化方式。本文稍后将对此进行详细介绍。 @@ -118,7 +118,7 @@ pub fn build(b: *std.build.Builder) void { 这将始终在当前机器的调试模式下编译,因此对于初学者来说,这可能就足够了。但如果你想开始发布你的项目,你可能需要启用交叉编译: -# 交叉编译 +## 交叉编译 交叉编译是通过设置程序的目标和编译模式来实现的 @@ -187,7 +187,7 @@ zig build -Doptimize=ReleaseSmall 但我们仍然必须调用 zig build 编译,因为默认调用仍然没有任何作用。让我们改变一下! -# 安装工件 +## 安装工件 要安装任何东西,我们必须让它依赖于构建器的安装步骤。该步骤是已创建的,可通过 Builder.getInstallStep() 访问。我们还需要创建一个新的 InstallArtifactStep,将我们的 exe 文件复制到安装目录(通常是 zig-out) @@ -254,7 +254,7 @@ pub fn build(b: *std.build.Builder) void { 现在,从理解初始构建脚本到完全扩展,还缺少一个部分: -# 运行已构建的应用程序 +## 运行已构建的应用程序 为了开发用户体验和一般便利性,从构建脚本中直接运行程序是非常实用的。这通常是通过运行步骤实现的,可以通过 zig build run 调用。 @@ -328,7 +328,7 @@ pub fn build(b: *std.build.Builder) void { zig build run -- -o foo.bin foo.asm ``` -# 结论 +## 结论 本系列的第一章应该能让你完全理解本文开头的构建脚本,并能创建自己的构建脚本。 diff --git a/content/post/2023-12-28-zig-build-explained-part2.smd b/content/post/2023-12-28-zig-build-explained-part2.smd index b9078c4..d68c0ef 100644 --- a/content/post/2023-12-28-zig-build-explained-part2.smd +++ b/content/post/2023-12-28-zig-build-explained-part2.smd @@ -9,15 +9,15 @@ > - 原文链接: https://zig.news/xq/zig-build-explained-part-2-1850 > - API 适配到 Zig 0.11.0 版本 -# 注释 +## 注释 从现在起,我将只提供一个最精简的 build.zig,用来说明解决一个问题所需的步骤。如果你想了解如何将所有这些文件粘合到一个构建文件中,请阅读本系列[第一篇文章](2023-12-24-zig-build-explained-part1)。 -# 在命令行上编译 C 代码 +## 在命令行上编译 C 代码 Zig 有两种编译 C 代码的方法,而且这两种很容易混淆。 -## 使用 zig cc +### 使用 zig cc Zig 提供了 LLVM c 编译器 clang。第一种是 zig cc 或 zig c++,它是与 clang 接近 1:1 的前端。由于我们无法直接从 build.zig 访问这些功能(而且我们也不需要!),所以我将在快速的介绍这个主题。 @@ -41,7 +41,7 @@ zig cc -o example.exe -target x86_64-windows-gnu buffer.c main.c 如你所见,只需向 -target 传递目标三元组,就能调用交叉编译。只需确保所有外部库都已准备好进行交叉编译即可! -# 使用 zig build-exe 和其他工具 +## 使用 zig build-exe 和其他工具 使用 Zig 工具链构建 C 项目的另一种方法与构建 Zig 项目的方法相同: @@ -59,7 +59,7 @@ zig build-exe -lc -target x86_64-windows-gnu main.c buffer.c 你会发现,使用这条编译命令,Zig 会自动在输出文件中附加 .exe 扩展名,并生成 .pdb 调试数据库。如果你在此处传递 --name example,输出文件也会有正确的 .exe 扩展名,所以你不必考虑这个问题。 -# 用 build.zig 创建 C 代码 +## 用 build.zig 创建 C 代码 那么,我们如何用 build.zig 来构建上面的两个示例呢? @@ -112,15 +112,15 @@ exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("buffer.c"), .flags = exe.addCSourceFile(.{.file = std.build.LazyPath.relative("buffer.c"), .flags = &.{"-fno-sanitize=undefined"}}); ``` -# 使用外部库 +## 使用外部库 通常情况下,C 项目依赖于其他库,这些库通常预装在 Unix 系统中,或通过软件包管理器提供。 为了演示这一点,我们创建一个小工具,通过 curl 库下载文件,并将文件内容打印到标准输出: ```c -#include -#include +##include +##include static size_t writeData(void *ptr, size_t size, size_t nmemb, FILE *stream) { size_t written; @@ -181,7 +181,7 @@ zig build ./zig-out/bin/downloader https://mq32.de/public/ziggy.txt ``` -# 配置路径 +## 配置路径 由于我们不能在交叉编译项目中使用 pkg-config,或者我们想使用预编译的专用库(如 BASS 音频库),因此我们需要配置包含路径和库路径。 @@ -220,7 +220,7 @@ pub fn build(b: *std.Build) void { addIncludePath 和 addLibraryPath 都可以被多次调用,以向编译器添加多个路径。这些函数不仅会影响 C 代码,还会影响 Zig 代码,因此 @cImport 可以访问包含路径中的所有头文件。 -# 每个文件的包含路径 +## 每个文件的包含路径 因此,如果我们需要为每个 C 文件设置不同的包含路径,我们就需要用不同的方法来解决这个问题: 由于我们仍然可以通过 addCSourceFile 传递任何 C 编译器标志,因此我们也可以在这里手动设置包含目录。 @@ -252,7 +252,7 @@ pub fn build(b: *std.Build) void { 上面的示例非常简单,所以你可能会想为什么需要这样的东西。答案是,有些库的头文件名称非常通用,如 api.h 或 buffer.h,而您希望使用两个共享头文件名称的不同库。 -# 构建 C++ 项目 +## 构建 C++ 项目 到目前为止,我们只介绍了 C 文件,但构建 C++ 项目并不难。你仍然可以使用 addCSourceFile,但只需传递一个具有典型 C++ 文件扩展名的文件,如 cpp、cxx、c++ 或 cc: @@ -285,7 +285,7 @@ pub fn build(b: *std.Build) void { 这就是构建 C++ 文件所需的全部知识,没有什么更神奇的了。 -# 指定语言版本 +## 指定语言版本 试想一下,如果你创建了一个庞大的项目,其中的 C 或 C++ 文件有新有旧,而且可能是用不同的语言标准编写的。为此,我们可以使用编译器标志来传递 -std=c90 或 -std=c++98: @@ -320,7 +320,7 @@ pub fn build(b: *std.Build) void { } ``` -# 条件编译 +## 条件编译 与 Zig 相比,C 和 C++ 的条件编译方式非常繁琐。由于缺乏惰性求值的功能,有时必须根据目标环境来包含/排除文件。你还必须提供宏定义来启用/禁用某些项目功能。 @@ -373,7 +373,7 @@ pub fn build(b: *std.Build) void { 有条件地包含文件就像使用 if 一样简单,你可以这样做。只要不根据你想在构建脚本中定义的任何约束条件调用 addCSourceFile 即可。只包含特定平台的文件?看看上面的脚本就知道了。根据系统时间包含文件?也许这不是个好主意,但还是有可能的! -# 编译大型项目 +## 编译大型项目 由于大多数 C(更糟糕的是 C++)项目都有大量文件(SDL2 有 411 个 C 文件和 40 个 C++ 文件),我们必须找到一种更简单的方法来编译它们。调用 addCSourceFile 400 次并不能很好地扩展。 @@ -478,7 +478,7 @@ pub fn build(b: *std.build.Builder) !void { 注意:其他构建系统会考虑文件名,而 Zig 系统不会!例如,在一个 qmake 项目中不能有两个名为 data.c 的文件!Zig 并不在乎,你可以添加任意多的同名文件,只要确保它们在不同的文件夹中就可以了 😏。 -# 编译 Objective C +## 编译 Objective C 我完全忘了!Zig 不仅支持编译 C 和 C++,还支持通过 clang 编译 Objective C! @@ -514,7 +514,7 @@ pub fn build(b: *std.Build) void { 在这里,链接 libc 是隐式的,因为添加框架会自动强制链接 libc。是不是很酷? -# 混合使用 C 和 Zig 源代码 +## 混合使用 C 和 Zig 源代码 现在,是最后一章: 混合 C 代码和 Zig 代码! @@ -554,7 +554,7 @@ pub fn build(b: *std.Build) void { 您应用程序的入口点现在必须在 Zig 代码中,因为根文件必须导出一个 pub fn main(...) ……。 因此,如果你想将 C 项目中的代码移植到 Zig 中,你必须将 argc 和 argv 转发到你的 C 代码中,并将 C 代码中的 main 重命名为其他函数(例如 oldMain),然后在 Zig 中调用它。如果需要 argc 和 argv,可以通过 std.process.argsAlloc 获取。或者更好: 在 Zig 中重写你的入口点,然后从你的项目中移除一些 C 语言! -# 结论 +## 结论 假设你只编译一个输出文件,那么现在你应该可以将几乎所有的 C/C++ 项目移植到 build.zig。 diff --git a/content/post/2023-12-29-zig-build-explained-part3.smd b/content/post/2023-12-29-zig-build-explained-part3.smd index 64371a8..b69f3e6 100644 --- a/content/post/2023-12-29-zig-build-explained-part3.smd +++ b/content/post/2023-12-29-zig-build-explained-part3.smd @@ -11,17 +11,17 @@ 从现在起,我将只提供一个最精简的 build.zig,用来说明解决一个问题所需的步骤。如果你想了解如何将所有这些文件粘合到一个构建文件中,请阅读本系列[第一篇文章](2023-12-24-zig-build-explained-part1)。 -# 复合项目 +## 复合项目 有很多简单的项目只包含一个可执行文件。但是,一旦开始编写库,就必须对其进行测试,通常会编写一个或多个示例应用程序。当人们开始使用外部软件包、C 语言库、生成代码等时,复杂性也会随之上升。 本文试图涵盖所有这些用例,并将解释如何使用 build.zig 来编写多个程序和库。 -# 软件包 +## 软件包 译者:此处代码和说明,需要 zig build-exe --pkg-begin,但是在 0.11 已经失效。所以删除。 -# 库 +## 库 但 Zig 也知道库这个词。但我们不是已经讨论过外部库了吗? @@ -36,7 +36,7 @@ 在 Zig 中,我们需要导入库的头文件,如果头文件在 Zig 中,则使用包,如果是 C 语言头文件,则使用 @cImport。 -# 工具 +## 工具 如果我们的项目越来越多,那么在构建过程中就需要使用工具。这些工具通常会完成以下任务: @@ -48,7 +48,7 @@ 但我们如何在 build.zig 中完成这些工作呢? -# 添加软件包 +## 添加软件包 添加软件包通常使用 LibExeObjStep 上的 addPackage 函数。该函数使用一个 std.build.Pkg 结构来描述软件包的外观: @@ -98,7 +98,7 @@ exe.addModule("lola",pkgs.lola); exe.addModule("args",pkgs.args); ``` -# 添加库 +## 添加库 添加库相对容易,但我们需要配置更多的路径。 @@ -106,7 +106,7 @@ exe.addModule("args",pkgs.args); 假设我们要将 libcurl 链接到我们的项目,因为我们要下载一些文件。 -## 系统库 +### 系统库 对于 unixoid 系统,我们通常可以使用系统软件包管理器来链接系统库。方法是调用 linkSystemLibrary,它会使用 pkg-config 自行找出所有路径: @@ -137,7 +137,7 @@ pub fn build(b: *std.Build) void { 对于 Linux 系统,这是链接外部库的首选方式。 -## 本地库 +### 本地库 不过,您也可以链接您作为二进制文件提供商的库。为此,我们需要调用几个函数。首先,让我们来看看这样一个库是什么样子的: @@ -167,7 +167,7 @@ include 我们可以看到,vendor/libcurl/include 路径包含我们的头文件,vendor/libcurl/lib 文件夹包含一个静态库(libcurl.a)和一个共享/动态库(libcurl.so)。 -## 动态链接 +### 动态链接 要链接 libcurl,我们需要先添加 include 路径,然后向 zig 提供库的前缀和库名:(todo 代码有待验证,因为 curl 可能需要自己编译自己生成 static lib) @@ -197,7 +197,7 @@ addLibraryPath 对库文件也有同样的作用。这意味着 Zig 现在也会 最后,linkSystemLibrary 会告诉 Zig 搜索名为 "curl "的库。如果你留心观察,就会发现上面列表中的文件名是 libcurl.so,而不是 curl.so。在 unixoid 系统中,库文件的前缀通常是 lib,这样就不会将其传递给系统。在 Windows 系统中,库文件的名字应该是 curl.lib 或类似的名字。 -# 静态链接 +## 静态链接 当我们要静态链接一个库时,我们必须采取一些不同的方法: @@ -250,7 +250,7 @@ pub fn build(b: *std.build.Builder) void { 我们可以继续静态链接越来越多的库,并拉入完整的依赖关系树。 -# 通过源代码链接库 +## 通过源代码链接库 不过,我们还有一种与 Zig 工具链截然不同的链接库方式: @@ -293,7 +293,7 @@ pub fn build(b: *std.build.Builder) void { 这一点尤其方便,因为我们可以使用 setTarget 和 setBuildMode 从任何地方编译到任何地方。 -# 使用工具 +## 使用工具 在工作流程中使用工具,通常是在需要以 bison、flex、protobuf 或其他形式进行预编译时。工具的其他用例包括将输出文件转换为不同格式(如固件映像)或捆绑最终应用程序。 @@ -352,7 +352,7 @@ size 是一个很好的工具,它可以输出有关可执行文件代码大小 如您所见,我们在这里使用了 addArtifactArg,因为 addSystemCommand 只会返回一个 std.build.RunStep。这样,我们就可以增量构建完整的命令行,包括任何 LibExeObjStep 输出、FileSource 或逐字参数。 -# 全新工具 +## 全新工具 最酷的是 我们还可以从 LibExeObjStep 获取 std.build.RunStep: @@ -389,7 +389,7 @@ pub fn build(b: *std.build.Builder) void { 调用 zig build pack 时,我们将运行 tools/pack.zig。这很酷,因为我们还可以从头开始编译所需的工具。为了获得最佳的开发体验,你甚至可以从源代码编译像 bison 这样的 "外部 "工具,这样就不会依赖系统了! -# 将所有内容放在一起 +## 将所有内容放在一起 一开始,所有这些都会让人望而生畏,但如果我们看一个更大的 build.zig 实例,就会发现一个好的构建文件结构会给我们带来很大帮助。 @@ -491,7 +491,7 @@ pub fn build(b: *std.build.Builder) void { 两者都是为了在主机平台上运行,而不是在目标机器上。 此外,deploy_tool 还设置了固定的编译模式,因为我们希望快速编译,即使我们编译的是应用程序的调试版本。 -# 总结 +## 总结 看完这一大堆文字,你现在应该可以构建任何你想要的项目了。我们已经学会了如何编译 Zig 应用程序,如何为其添加任何类型的外部库,甚至如何为发布管理对应用程序进行后处理。 diff --git a/content/post/2024-01-12-how-to-release-your-zig-applications.smd b/content/post/2024-01-12-how-to-release-your-zig-applications.smd index 57466d7..d72f9a1 100644 --- a/content/post/2024-01-12-how-to-release-your-zig-applications.smd +++ b/content/post/2024-01-12-how-to-release-your-zig-applications.smd @@ -10,84 +10,83 @@ > - API 适配到 Zig 0.12.0 版本 > - 本文配套代码在[这里](https://github.com/zigcc/zigcc.github.io/tree/main/examples/20240112)找到 -你刚用 Zig 写了一个应用程序,并希望其他人使用它。 -让用户方便使用的一种方式是为他们提供应用程序的预构建可执行文件。 -接下来,我将讨论一个好的发版流程所需要正确处理的两个主要事项。 +You've just written an application in Zig and want others to use it. +A convenient way for users to use your application is to provide a pre-built executable file. +Next, I'll discuss the two main things that need to be handled correctly in a good release process. -# 为什么提供预构建的可执行文件? +## Why provide pre-built executable files? -鉴于 C/C++ 依赖系统如何工作(或者说 _不工作_),对于某些 C/C++ 项目来说, -提供预编译好的的可执行文件几乎是必须的, -否则,普通人将陷入构建系统和配置系统的泥潭, -而这些系统的数量还要乘以项目的依赖数量。 -使用 Zig 的话就不应该这样,因为 Zig 构建系统(加上即将推出的 Zig 包管理器)将能够处理一切,这意味着大多数编写良好的应用程序应该只需运行 `zig build` 即可成功构建。 +Given how C/C++ dependencies work (or don't work), for some C/C++ projects, +providing pre-compiled executable files is almost a necessity, +otherwise, ordinary people will get stuck in the build system configuration and +the number of systems to multiply these by is the number of project dependencies. +Using Zig shouldn't be like this, because Zig build system (plus the upcoming Zig package manager) will be able to handle everything, meaning most well-written applications should just run `zig build` to succeed. -话虽如此,你的应用程序越受欢迎,用户就越不关心它是用哪种语言编写的。 -你的用户不想安装 Zig 并运行构建过程就能轻松使用应用程序(99%的情况下,稍后会讲到剩下的 1%), -因此最好还是预先构建你的应用程序。 +That said, the more popular your application is, the less users will care what language it's written in. +Your users don't want to install Zig and run the build process to easily use your application (99% of the time, the rest 1% will be discussed later), +so it's still better to pre-build your application. -# `zig build` vs `zig build-exe` +## `zig build` vs `zig build-exe` -在本文中,我们将看到如何为 Zig 项目制作、发布构建, -因此值得花一点时间来完全理解 Zig 构建系统和命令行之间的关系。 +In this article, we'll see how to make, release a build for a Zig project, +so it's worth spending a little time to fully understand the relationship between Zig build system and command line. -如果你有一个非常简单的 Zig 应用程序(例如,单个文件,无任何依赖), -构建项目最简单的方式是使用 `zig build-exe myapp.zig`, -这会在当前路径下创建一个可执行文件。 +If you have a very simple Zig application (for example, a single file, no dependencies), +the simplest way to build a project is to use `zig build-exe myapp.zig`, +which will create an executable file in the current path. -随着项目的增长,特别是开始有依赖之后,你可能想添加一个 `build.zig` 文件, -并开始用到 Zig 构建系统。一旦你开始这么做,你就可以完全控制命令行参数来影响构建过程。 +As the project grows, especially when dependencies start, you might want to add a `build.zig` file, +and start using Zig build system. Once you start doing this, you can completely control command line parameters to affect the build process. -你可以使用 `zig init-exe` 来了解基线 `build.zig` 文件的样子。 -请注意,文件中的每一行代码都是显示定义,从而影响 `zig build` 子命令的行为。 +You can use `zig init-exe` to see what a baseline `build.zig` file looks like. +Note that every line of code in the file is explicitly defined, thus affecting the behavior of `zig build` subcommand. -最后一点需要注意的是,尽管使用 `zig build` 和 `zig build-exe` 时命令行参数会有所不同, -但在构建 Zig 代码方面,两者是等价的。更具体地说,尽管 Zig 构建可以调用任意命令, -并做其他可能根本与 Zig 代码无关的事情,但在构建 Zig 代码方面, -`zig build` 所做的一切就是为 `build-exe` 准备命令行参数。 -这意味着,在编译 Zig 代码方面,`zig build`(假定 `build.zig` 中的代码是正确的) -和 `zig build-exe` 之间是一一对应关系。唯一的区别只是便利性。 +The last thing to note is that although command line parameters will be different when using `zig build` and `zig build-exe`, +in building Zig code, both are equivalent. More specifically, although Zig build can call arbitrary commands, +and do other things that may have nothing to do with Zig code, in building Zig code, +what `zig build` does is to prepare command line parameters for `build-exe`. +This means that in compiling Zig code, `zig build` (assuming the code in `build.zig` is correct) +and `zig build-exe` are one-to-one correspondence. The only difference is convenience. -# 构建模式 +## Build mode -使用 `zig build` 或 `zig build-exe myapp.zig` 构建一个 Zig 项目时, -默认得到是一个调试构建的可执行文件。调试构建主要是为了开发方便,因而,通常被认为不适合发版。 -调试构建旨在牺牲运行性能(运行更慢)来提高构建速度(编译更快), -不久, Zig 编译器将通过引入增量编译和就地二进制补丁(in-place binary patching) -来让这种权衡变得更加明显。 +When building a Zig project with `zig build` or `zig build-exe myapp.zig`, +the default is a debug build executable file. Debug build is mainly for development convenience, so it's usually considered unsuitable for release. +Debug build aims to sacrifice running performance (running slower) for build speed (compiling faster), +soon, Zig compiler will make this trade-off more obvious by introducing incremental compilation and in-place binary patching. -Zig 目前有三种主要的发版构建模式:`ReleaseSafe`、`ReleaseFast` 和 `ReleaseSmall`。 +Zig currently has three main release build modes: `ReleaseSafe`, `ReleaseFast` and `ReleaseSmall`. -`ReleaseSafe` 应被视为发版时使用的主要模式:尽管使用了优化, -但仍保留了某些安全检查(例如,溢出和数组越界), -在发布处理不可控输入源(如互联网)的软件时,这些开销是绝对值得的。 +`ReleaseSafe` should be considered the main mode to use when releasing: although optimizations are used, +it still retains some safety checks (for example, overflow and array bounds), +these overheads are absolutely worth it when releasing software that handles uncontrollable input sources (like the internet). -`ReleaseFast` 旨在用于性能是主要关注点的软件, -例如视频游戏。这种构建模式不仅禁用了上述安全检查, -而且为了进行更激进的优化,它还假设代码中不存在这类编程错误。 +`ReleaseFast` is intended for software where performance is the main concern, +for example video games. This build mode not only disables the above safety checks, +but also assumes that there are no such programming errors in the code. -`ReleaseSmall` 类似于 `ReleaseFast`(即,没有安全检查), -但它不是优先考虑性能,而是尝试最小化可执行文件大小。 -例如,这是一种对于 WebAssembly 来说非常有意义的构建模式, -因为你希望可执行文件尽可能小,而沙箱运行环境已经提供了很多安全保障。 +`ReleaseSmall` is similar to `ReleaseFast` (i.e., no safety checks), +but it's not prioritized for performance, but rather for trying to minimize the executable file size. +For example, this is a very meaningful build mode for WebAssembly, +because you want the executable file to be as small as possible, and the sandbox runtime environment has provided a lot of security guarantees. -# 如何设置构建模式 +## How to set build mode -使用 `zig build-exe` 时,你可以添加 `-O ReleaseSafe` -(或 `ReleaseFast`,或 `ReleaseSmall`)以获得相应的构建模式。 +When using `zig build-exe`, you can add `-O ReleaseSafe` +(or `ReleaseFast`, or `ReleaseSmall`) to get the corresponding build mode. -使用 `zig build` 时,取决于构建脚本的配置。默认构建脚本将包含以下代码行: +When using `zig build`, it depends on the configuration of the build script. The default build script will include the following code lines: ```zig -// standardReleaseOptions 允许我们在运行 zig build 时,手动选择需要构建的目标平台和架构 -// 默认情况下为本机构建 +// standardReleaseOptions allows us to manually select the target platform and architecture when running zig build +// Default is for this architecture const target = b.standardTargetOptions(.{}); -// standardOptimizeOption 允许我们在运行 zig build 时,手动选择构建模式 -// 默认情况下为 Debug +// standardOptimizeOption allows us to manually select build mode when running zig build +// Default is Debug const optimize = b.standardOptimizeOption(.{}); -// 标准构建一个可执行二进制程序的步骤 +// Standard steps to build a executable binary const exe = b.addExecutable(.{ .name = "zig", .root_source_file = .{ .path = "src/main.zig" }, @@ -96,47 +95,47 @@ const exe = b.addExecutable(.{ }); ``` -这是你在命令行中指定发布模式的方式:`zig build -Doptimize=ReleaseSafe`(或 -`-Doptimize=ReleaseFast`,或 `-Doptimize=ReleaseSmall`)。 +This is how you specify release mode in command line: `zig build -Doptimize=ReleaseSafe` (or +`-Doptimize=ReleaseFast`, or `-Doptimize=ReleaseSmall`). -# 选择正确的构建目标 +## Choose the correct build target -现在,我们已经选择了正确的发版模式,是时候考虑构建目标了。 -显而易见,如果使用的平台和构建平台不相同时,需要指定相应的构建目标, -但即使只打算为同一平台发版,也还是需要注意。 +Now, we've chosen the correct release mode, it's time to consider build target. +Obviously, if the platform used and build platform are different, the corresponding build target needs to be specified, +but even if you just intend to release for the same platform, you still need to pay attention. -为了方便起见,假定你用的是 Windows 10,并试图为使用 Windows 10 的朋友构建可执行文件。 -最想当然的方式是直接调用 `zig build` 或 `zig build-exe`(参见前文关于两个命令之间的差异与相似之处),然后将生成的可执行文件发送给你的朋友。 +For convenience, assume you're using Windows 10 and trying to build an executable file for your friends using Windows 10. +The most straightforward way is to directly call `zig build` or `zig build-exe` (see the difference and similarity between the two commands above), and then send the generated executable file to your friends. -如果这样做,有时它会工作,但有时它会因`非法指令`(或类似错误)而崩溃。这到底发生了什么? +If you do this, sometimes it works, but sometimes it crashes due to `illegal instruction` (or similar error). What's happening? -# CPU 特性 +## CPU features -在构建时如果不指定构建目标,Zig 将面向当前的机器进行构建优化, -这意味着将利用当前 CPU 支持的所有指令集。如果 CPU 支持 AVX 扩展, -那么 Zig 将使用它来执行 SIMD 操作。但不幸的是, -这也意味着,如果你朋友的 CPU 没有 AVX 扩展,那么应用程序将崩溃, -因为这个可执行文件确实包含非法指令。 +If build target is not specified when building, Zig will build for the current machine, +which means it will use all instruction sets supported by the current CPU. If the CPU supports AVX extension, +then Zig will use it to perform SIMD operations. But unfortunately, +this also means that if your friend's CPU doesn't support AVX extension, the application will crash, +because this executable file indeed contains illegal instructions. -解决这个问题最简单的方法是:始终在进行发布时指定一个构建目标。 -没错,如果你指定你想要为 `x86-64-linux` 构建, -Zig 将设定一个与该系列所有 CPU 完全兼容的基线 CPU。 +The simplest way to solve this problem is: always specify a build target when releasing. +Yes, if you specify you want to build for `x86-64-linux`, +Zig will set a baseline CPU that is fully compatible with all CPUs in that series. -如果你想微调指令集的选择,你可以查看 `zig build` 的 `-Dcpu` 和 `zig build-exe` 的 -`-mcpu`。我不会在这篇文章中更多地涉及这些细节。 +If you want to fine-tune instruction set selection, you can check `zig build`'s `-Dcpu` and `zig build-exe`'s +`-mcpu`. I won't cover these details more in this article. -实操中,下面的命令行将是你为 Arm macOS 发版时会用到的构建命令: +In practice, the following command line will be the build command you'll use when releasing for Arm macOS: ```zig $ zig build -Dtarget=aarch64-macos $ zig build-exe myapp.zig -target aarch64-macos ``` -请注意,目前在使用 `zig build` 时 `=` 是必需的, -而在使用 `build-exe` 时它不起作用(即你必须在 `-target` 及其值之间放一个空格)。 -希望这些怪异的地方在不久将来会得到清理。 +Note that currently `=` is required when using `zig build`, +while it doesn't work when using `build-exe` (i.e., you must put a space between `-target` and its value). +I hope these weird places will be cleaned up in the near future. -其它一些相关的构建目标: +Other related build targets: ```zig x86-64-linux // uses gnu libc @@ -147,21 +146,22 @@ x86-64-windows-msvc // uses MSVC headers but they need to be present in your sys wasm32-freestanding // you will have to use build-obj since wasm modules are not full exes ``` -你可以通过调用 `zig targets` 看到 Zig 支持的目标 CPU 和 -操作系统(以及 libc 和指令集)的完整列表。温馨提示:这是一个很长的列表。 +You can see the complete list of Zig supported target CPUs and +operating systems (as well as libc and instruction sets) by calling `zig targets`. +Reminder: this is a very long list. -最后,别忘了 `build.zig` 里的一切都必须明确定义,因此目标选项可以通过以下几行代码设置: +Finally, don't forget everything in `build.zig` must be explicitly defined, so target options can be set through the following code lines: ```zig -// standardReleaseOptions 允许我们在运行 zig build 时,手动选择需要构建的目标平台和架构 -// 默认情况下为本机构建 +// standardReleaseOptions allows us to manually select the target platform and architecture when running zig build +// Default is for this architecture const target = b.standardTargetOptions(.{}); -// standardOptimizeOption 允许我们在运行 zig build 时,手动选择构建模式 -// 默认情况下为 Debug +// standardOptimizeOption allows us to manually select build mode when running zig build +// Default is Debug const optimize = b.standardOptimizeOption(.{}); -// 标准构建一个可执行二进制程序的步骤 +// Standard steps to build a executable binary const exe = b.addExecutable(.{ .name = "zig", .root_source_file = .{ .path = "src/main.zig" }, @@ -170,13 +170,13 @@ const exe = b.addExecutable(.{ }); ``` -这也意味着如果你想添加其他限制或以某种方式改变构建时应该如何指定目标, -你可以通过添加自己的代码来实现。 +This also means if you want to add other restrictions or change how to specify target when building, +you can achieve this by adding your own code. -# 结束语 +## Conclusion -现在你已经了解了在进行发布构建时需要确保正确的事项:选择一个发布优化模式并选择正确的构建目标, -包括为你正在构建的同一系统进行发版构建。 +Now you've learned the things you need to ensure correctly when releasing a build: choose a release optimization mode and choose the correct build target, +including building for the same system you're building. -这最后一点的一个有趣含义是,对于你的一些用户(通常情况下为 1%,乐观估计), -从头开始构建程序实际上更为可取,以确保他们充分利用其 CPU 的能力。 +One interesting implication of this last point is that for some of your users (usually 1% on average), +it's actually more beneficial to start building the program from scratch to ensure they make full use of their CPU capabilities. diff --git a/content/post/2024-04-06-zig-cpp.smd b/content/post/2024-04-06-zig-cpp.smd index 8b12c40..4328542 100644 --- a/content/post/2024-04-06-zig-cpp.smd +++ b/content/post/2024-04-06-zig-cpp.smd @@ -6,11 +6,11 @@ .draft = false, --- -尽管 Zig 社区宣称 Zig 语言是一个更好的 C (better C),但是我个人在学习 Zig 语言时经常会“触类旁通”C++。在这里列举一些例子来说明我的一些体会,可能会有一些不正确的地方,欢迎批评指正。 +尽管 Zig 社区宣称 Zig 语言是一个更好的 C (better C),但是我个人在学习 Zig 语言时经常会"触类旁通"C++。在这里列举一些例子来说明我的一些体会,可能会有一些不正确的地方,欢迎批评指正。 -# “元能力” vs “元类型” +## "元能力" vs "元类型" -在我看来,C++的增强方式是希望赋予语言一种“元能力”,能够让人重新发明新的类型,使得使用 C++的程序员使用自定义的类型,进行一种类似于“领域内语言”(DSL)编程。一个通常的说法就是 C++中任何类型定义都像是在模仿基本类型`int`。比如我们有一种类型 T,那么我们则需要定义 T 在以下几种使用场景的行为: +在我看来,C++的增强方式是希望赋予语言一种"元能力",能够让人重新发明新的类型,使得使用 C++的程序员使用自定义的类型,进行一种类似于"领域内语言"(DSL)编程。一个通常的说法就是 C++中任何类型定义都像是在模仿基本类型`int`。比如我们有一种类型 T,那么我们则需要定义 T 在以下几种使用场景的行为: ```cpp T x; //构造 @@ -20,13 +20,13 @@ x = y; //x的类型是T,复制运算符重载,当然也有可能是移动运 //以及一大堆其他行为,比如析构等等。 ``` -通过定义各种行为,程序员可以用 C++去模拟基础类型`int`,自定义的创造新类型。但是 Zig 却采取了另一条路,这里我觉得 Zig 的取舍挺有意思,即它剥夺了程序员定义新类型的能力,只遵循 C 的思路,即`struct`就是`struct`,他和`int`就是不一样的,没有必要通过各种运算符重载来制造一种“幻觉”,模拟`int`。相反,Zig 吸收现代语言中最有用的“元类型”,比如`slice`,`tuple`,`tagged union`等作为语言内置的基本类型,从这一点上对 C 进行增强。虽然这样降低了语言的表现力,但是却简化了设计,降低了“心智负担”。 +通过定义各种行为,程序员可以用 C++去模拟基础类型`int`,自定义的创造新类型。但是 Zig 却采取了另一条路,这里我觉得 Zig 的取舍挺有意思,即它剥夺了程序员定义新类型的能力,只遵循 C 的思路,即`struct`就是`struct`,他和`int`就是不一样的,没有必要通过各种运算符重载来制造一种"幻觉",模拟`int`。相反,Zig 吸收现代语言中最有用的"元类型",比如`slice`,`tuple`,`tagged union`等作为语言内置的基本类型,从这一点上对 C 进行增强。虽然这样降低了语言的表现力,但是却简化了设计,降低了"心智负担"。 -比如 Zig 里的`tuple`,C++里也有`std::tuple`。当然,`std::tuple`是通过一系列的模板元编程的方式实现的,但是这个在 Zig 里是内置的,因此写代码时出现语法错误,Zig 可以直接告诉你是`tuple`用的不对,但是 C++则会打印很多错误日志。再比如`optional`,C++里也有`std::optinonal`,Zig 里只用`?T`。C++里有`std::variant`,而 Zig 里有`tagged union`。当然我们可以说,C++因为具备了这种元能力,当语法不足够“甜”时,我们可以发明新的轮子,但是代价就是系统愈发的复杂。而 Zig 则持续保持简单。 +比如 Zig 里的`tuple`,C++里也有`std::tuple`。当然,`std::tuple`是通过一系列的模板元编程的方式实现的,但是这个在 Zig 里是内置的,因此写代码时出现语法错误,Zig 可以直接告诉你是`tuple`用的不对,但是 C++则会打印很多错误日志。再比如`optional`,C++里也有`std::optinonal`,Zig 里只用`?T`。C++里有`std::variant`,而 Zig 里有`tagged union`。当然我们可以说,C++因为具备了这种元能力,当语法不足够"甜"时,我们可以发明新的轮子,但是代价就是系统愈发的复杂。而 Zig 则持续保持简单。 -不过话说回来,很多底层系统的开发需求往往和这种类型系统的构建相悖,比如如果你的类型就是一个`int`的封装,那么即使发生拷贝你也无所谓性能开销。但是如果是一个`struct`,那么通常情况下,你会比较 care 拷贝,而可能考虑“移动”之类的手段。这个时候各种 C++的提供的幻觉,就成了程序员开发的绊脚石,经常你需要分析一段 C++表达式里到底有没有发生拷贝,他是左值还是右值,其实你在写 C 语言的时候也很少去考虑了这些,你在 Zig 里同样也不需要。 +不过话说回来,很多底层系统的开发需求往往和这种类型系统的构建相悖,比如如果你的类型就是一个`int`的封装,那么即使发生拷贝你也无所谓性能开销。但是如果是一个`struct`,那么通常情况下,你会比较 care 拷贝,而可能考虑"移动"之类的手段。这个时候各种 C++的提供的幻觉,就成了程序员开发的绊脚石,经常你需要分析一段 C++表达式里到底有没有发生拷贝,他是左值还是右值,其实你在写 C 语言的时候也很少去考虑了这些,你在 Zig 里同样也不需要。 -# 类型系统 +## 类型系统 C 语言最大弊病就是没有提供标准库,C++的标准库你要是能看懂,得具备相当的 C++的语法知识,但是 Zig 的标准库几乎不需要文档就能看懂。这其实是因为,在 C++里,类型不是一等成员(first class member),因此实现一些模版元编程算法特别不直观。但是在 Zig 里,`type`就是一等成员,比如你可以写: @@ -61,9 +61,9 @@ struct Some { Some::OutputType ``` -相当于对于 InputType 调用一个 Some“函数”,然后输出一个 OutputType。 +相当于对于 InputType 调用一个 Some"函数",然后输出一个 OutputType。 -# 命令式 VS 声明式 +## 命令式 VS 声明式 比如实现一个函数,输入一个 bool 值,根据 bool 值,如果为真,那么输出 type A,如果为假那么输出 type B。 @@ -104,7 +104,7 @@ fn Fn(comptime A:type, comptime B: type) type { 我们再来看递归的列子。比如有一个类型的 list,我们需要返回其中第 N 个 type。同样,由于在 C++中,类型不是一等成员,因此我们不可能有一个`vector`的东东。那怎么办呢?方法就是直接把`type list`放在模板的参数列表里:`typename ...T`。 -于是,我们写出“函数原型”: +于是,我们写出"函数原型": ```cpp template @@ -163,6 +163,6 @@ pub fn main() void { 即这个也是完全命令式的。当然 C++20 之后也出现了`if constexpr`和`concept`来进一步简化模版元编程,C++的元编程也在向命令式的方向进化。 -# 结束语 +## 结束语 -尽管 Zig 目前“还不成熟”,但是学习 Zig,如果采用一种对照的思路,偶尔也会“触类旁通”C++,达到举一反三的效果。 +尽管 Zig 目前"还不成熟",但是学习 Zig,如果采用一种对照的思路,偶尔也会"触类旁通"C++,达到举一反三的效果。 diff --git a/content/post/2024-05-07-package-hash.smd b/content/post/2024-05-07-package-hash.smd index ee902fb..7eea64c 100644 --- a/content/post/2024-05-07-package-hash.smd +++ b/content/post/2024-05-07-package-hash.smd @@ -6,9 +6,11 @@ .draft = false, --- +## [package hash]($section.id('package-hash')) + > 原文地址:[build.zig.zon dependency hashes](https://zig.news/michalsieron/buildzigzon-dependency-hashes-47kj) -# 引言 +## 引言 作者 Michał Sieroń 最近在思考 `build.zig.zon` 中的依赖项哈希值的问题。这些哈希值都有相同的前缀,而这对加密哈希函数来说极其不同寻常。习惯性使用 Conda 和 Yocto 对下载的压缩包运行 sha256sum,但生成的摘要与 `build.zig.zon` 中的哈希值完全不同。 @@ -30,9 +32,9 @@ } ``` -以上摘录取自 [hexops/mach](https://github.com/hexops/mach/blob/bffc66800584123e2844c4bc4b577940806f9088/build.zig.zon#L13-L26) 项目。 +以上摘取自 [hexops/mach](https://github.com/hexops/mach/blob/bffc66800584123e2844c4bc4b577940806f9088/build.zig.zon#L13-L26) 项目。 -# 初步探索 +## 初步探索 经过一番探索,我找到了一个文档:[doc/build.zig.zon.md](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/doc/build.zig.zon.md),似乎没有任何线索指向它。而文档中对哈希有段简短的描述。 @@ -42,7 +44,7 @@ 该哈希值是基于一系列文件内容计算得出的,这些文件是在获取URL后并应用了路径规则后得到的。 这个字段是最重要的;一个包是的唯一性是由它的哈希值确定的,不同的 URL 可能对应同一个包。 -# 多重哈希 +## 多重哈希 在他们的网站上有一个很好的可视化展示,说明了这一过程: [多重哈希](https://multiformats.io/multihash/)。 @@ -50,7 +52,7 @@ 因此 `build.zig.zon` 中的哈希字段不仅包含了摘要(digest),还包含了一些元数据(metadata)。但即使我们丢弃了头部信息,得到的结果仍与下载的 `tar` 包的 `sha256` 摘要不相符。而这就涉及到了包含规则的问题。 -# 包含规则(inclusion rules) +## 包含规则(inclusion rules) 回到 [doc/build.zig.zon.md](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/doc/build.zig.zon.md) 文件,我们看到: @@ -91,7 +93,7 @@ pub fn includePath(self: Filter, sub_path: []const u8) bool { 除此之外,这个函数会检查 `sub_path` 是否被明确列出,或者是已明确列出的目录的子目录。 -# 计算哈希 +## 计算哈希 现在我们知道了 `build.zig.zon` 的包含规则,也知道使用了 `SHA256` 算法。但我们仍然不知道实际的哈希结果是如何得到的。例如,它可能是通过将所有包含的文件内容输入哈希器来计算的。所以让我们再仔细看看,也许我们可以找到答案。 @@ -119,7 +121,7 @@ try all_files.append(hashed_file); 跟踪 `workerHashFile`,我们看到它是 `hashFileFallible` 的一个简单包装,而后者看起来相当复杂。让我们来分解一下。 -# 单个文件的哈希计算 +## 单个文件的哈希计算 首先,会进行一些初始化设置,其中创建并用规整后的文件路径初始化了一个新的哈希器实例: @@ -167,7 +169,7 @@ hasher.update(link_name); 首先进行路径分隔符的规整,保证不同平台一致,之后将符号链接的目标路径输入 `hasher`。在 `hashFileFallible` 函数最后,把计算出的哈希值赋值给 `HashedFile` 对象的 `hash` 字段。 -# 组合哈希 +## 组合哈希 尽管有了单个文件的哈希值,但我们仍不知道如何得到最终的哈希。幸运的是,曙光就在眼前。 @@ -197,13 +199,13 @@ for (all_files.items) |hashed_file| { 在这里我们看到所有计算出的哈希被一个接一个地输入到一个新的哈希器中。在 `computeHash` 的最后,我们返回 `hasher.finalResult()`,现在我们明白哈希值是如何获得的了。 -# 最终多哈希值 +## 最终多哈希值 现在我们有了一个 `SHA256` 摘要,可以最终返回到 `main.zig`,在那里我们调用 [`Package.Manifest.hexDigest(fetch.actual_hash)`](https://github.com/ziglang/zig/blob/9d64332a5959b4955fe1a1eac793b48932b4a8a8/src/Package/Manifest.zig#L174)。在那里,我们将多哈希头写入缓冲区,之后是我们的组合摘要。 顺便说一下,我们看到所有哈希头都是 `1220` 并非巧合。这是因为 `Zig` [硬编码了 SHA256](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Manifest.zig#L3) - 0x12,它有 32 字节的摘要 - 0x20。 -# 总结 +## 总结 总结一下:最终哈希值是一个多哈希头 + `SHA256` 摘要。 @@ -213,7 +215,7 @@ for (all_files.items) |hashed_file| { 在实验这个之后,我有一个想法,我很惊讶 Zig 没有检查 `build.zig.zon` 中列出的所有文件是否存在。但这可能是另一天的话题了。 -# 译者注 +## 译者注 在使用本地包时,可以使用下面的命令进行 hash 问题的排查: diff --git a/content/post/2024-05-24-interface-idioms.smd b/content/post/2024-05-24-interface-idioms.smd index 8b6f7c5..57c676c 100644 --- a/content/post/2024-05-24-interface-idioms.smd +++ b/content/post/2024-05-24-interface-idioms.smd @@ -8,7 +8,7 @@ > 原文链接: https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj -# 引言 +## 引言 在 Java 和 Go 中,可以使用“接口”(一组方法或方法集)定义基于行为的抽象。通常接口包含所谓的虚表(`vtable`) 以实现动态分派。Zig 允许在结构体、枚举、联合和不透明类型中声明函数和方法,尽管 Zig 尚未支持接口作为一种语言特性。 @@ -31,7 +31,7 @@ Zig 标准库应用了一些代码习语或模式以达到类似效果。 完整代码位于[此仓库](https://github.com/yglcode/zig_interfaces),你可以使用 `zig test interfaces.zig` 运行它。 -# 背景设定 +## 背景设定 让我们使用经典的面向对象编程示例,创建一些形状:点(`Point`)、盒子(`Box`)和圆(`Circle`)。 @@ -86,7 +86,7 @@ fn init_data() struct { point: Point, box: Box, circle: Circle } { } ``` -# 接口1:枚举标签联合 +## 接口1:枚举标签联合 Loris Cro 在[“使用 Zig 0.10.0 轻松实现接口”](https://zig.news/kristoff/easy-interfaces-with-zig-0100-2hc5) 中介绍了使用枚举标签联合作为接口的方法。这是最简单的解决方案,尽管你必须在联合类型中显式列出所有“实现”该接口的变体类型。 @@ -126,7 +126,7 @@ test "union_as_intf" { } ``` -# 接口2:vtable 和动态分派的第一种实现 +## 接口2:vtable 和动态分派的第一种实现 Zig 已从最初基于嵌入式 `vtab` 和 `#fieldParentPtr()` 的动态分派切换到基于“胖指针”接口的以下模式; 请查阅此文章了解更多细节[“Allocgate 将在 Zig 0.9 中到来...”](https://pithlessly.github.io/allocgate.html)。 @@ -198,7 +198,7 @@ test "vtab1_as_intf" { } ``` -# 接口3:vtable 和动态分派的第二种实现 +## 接口3:vtable 和动态分派的第二种实现 在上述第一种实现中,通过 `Shape2.init()` 将 `Box` “转换”为接口 `Shape2` 时,会对 `box` 实例进行类型检查, 以确保其实现了 `Shape2` 的方法(包括名称的匹配签名)。第二种实现中有两个变化: @@ -271,7 +271,7 @@ test "vtab2_as_intf" { } ``` -# 接口4:使用嵌入式 vtab 和 @fieldParentPtr() 的原始动态分派 +## 接口4:使用嵌入式 vtab 和 @fieldParentPtr() 的原始动态分派 接口 `std.build.Step` 和所有构建步骤 `std.build.[RunStep, FmtStep, ...]` 仍然使用这种模式。 @@ -364,7 +364,7 @@ test "vtab3_embedded_in_struct" { } ``` -# 接口5:编译时的泛型接口 +## 接口5:编译时的泛型接口 所有上述接口都侧重于 `vtab` 和动态分派:接口值将隐藏其持有的具体值的类型。因此,你可以将这些接口值放入数组中并统一处理。 diff --git a/content/post/2024-06-10-zig-hashmap-1.smd b/content/post/2024-06-10-zig-hashmap-1.smd index ef350dc..79c68fe 100644 --- a/content/post/2024-06-10-zig-hashmap-1.smd +++ b/content/post/2024-06-10-zig-hashmap-1.smd @@ -24,7 +24,7 @@ pub fn put(self: *Self, key: K, value: V) Allocator.Error!void { 正如我所说,大部分繁重的工作都由 `std.HashMapUnmanaged` 完成,其他变体通过一个名为 `unmanaged` 的字段对其进行封装。 -# Unmanaged +## Unmanaged 在Zig标准库中随处可见的类型命名约定是 `unmanaged`。这种命名方式表明所涉及的类型不维护 `allocator`。任何需要分配内存的方法都会显式地将 `allocator` 作为参数传递。要实际看到这一点,可以考虑下面这个链表的例子: @@ -143,7 +143,7 @@ pub fn LinkedList(comptime T: type) type { 为了简化,本文的其余部分不会再提到 `unmanaged`。我们看到关于 `StringHashMap` 或 `AutoHashMap` 或 `HashMap` 的任何内容同样适用于它们的 Unmanaged 变体。 -# HashMap 与 AutoHashMap +## HashMap 与 AutoHashMap std.HashMap 是一个泛型类型,它接受两个类型参数:键的类型和值的类型。正如我们所见,哈希映射需要两个函数:hash 和 eql。这两个函数合起来被称为“上下文(context)”。这两个函数都作用于键,并且没有一个单一的 hash 或 eql 函数适用于所有类型。例如,对于整数键,eql 将是 `a_key == b_key`;而对于 `[]const u8` 键,我们希望使用 `std.mem.eql(u8, a_key, b_key)`。 @@ -234,7 +234,7 @@ pub const StringContext = struct { }; ``` -# 自定义上下文 +## 自定义上下文 我们将在第一部分结束时,直接使用 `HashMap`,这意味着提供我们自己的上下文。我们将从一个简单的例子开始:为不区分大小写的 ASCII 字符串创建一个 `HashMap`。我们希望以下内容输出:`Goku = 9000`。请注意,虽然我们使用键 `GOKU` 进行插入,但我们使用“goku”进行获取: @@ -379,7 +379,7 @@ pub fn hash(_: HashContext, u: User) u64 { 插入这两个函数,以上示例应该可以工作。 -# 结论 +## 结论 希望你现在对 Zig 的哈希表的实现以及如何在代码中利用它们有了更好的理解。在大多数情况下,`std.StringHashMap` 或 `std.AutoHashMap` 就足够了。但知道 `*Unmanaged` 变体的存在和目的,以及更通用的 `std.HashMap`,可能会派上用场。如果没有其他用途,现在文档和它们的实现应该更容易理解了。 diff --git a/content/post/2024-06-11-zig-hashmap-2.smd b/content/post/2024-06-11-zig-hashmap-2.smd index 3216dcb..e6ba02f 100644 --- a/content/post/2024-06-11-zig-hashmap-2.smd +++ b/content/post/2024-06-11-zig-hashmap-2.smd @@ -41,7 +41,7 @@ keys: values: 这个基本的可视化表示将贯穿本文大部分内容,并且不断强调条目的位置需要保持一致性和可预测性。即使哈希表需要在增长时从一个底层数组移动到另一个(即当填充因子达到一定阈值并要求扩大以容纳更多数据时),这一事实是我们将反复回顾的主题。 -# 值管理 +## 值管理 如果我们对上述代码片段进行扩展,并调用 `lookup.get("Paul")`,返回的值将是 `1234`。在处理像 `i32` 这样的原始类型时,很难直观地理解 `get` 方法和它的可选返回类型 `?i32` 或更通用的 `?V`(其中 `V` 表示任何值类型)之间的区别。考虑到这一点,让我们通过替换 `i32` 为一个封装了更多信息的 `User` 类型来展示这一概念: @@ -224,7 +224,7 @@ while (it.next()) |value_ptr| { 在最后一种情况下,由于我们存储的是 `User` 而不是 `*User`,我们的 `value_ptr` 是指向 `User` 的指针(不像之前那样是指向指针的指针)。 -# Keys +## Keys 我们可以开始和结束这一节:我们关于值的所有内容同样适用于键。这是100%正确的,但这在某种程度上不太直观。大多数开发人员很快就能理解,存储在哈希表中的堆分配的 `User` 实例有其自身的生命周期,需要显式管理/释放。但由于某些原因,这对于键来说并不那么明显。 @@ -337,7 +337,7 @@ if (lookup.fetchRemove(user.name)) |kv| { 对于大多数情况,在处理非原始键或值时,关键是当你调用哈希表的 `deinit` 时,你为键和值分配的任何内存不会被自动释放;你需要自己处理。 -# getOrPut +## getOrPut 虽然我们已经讨论过的内容有很多含义,但对我来说,直接暴露键和值指针的最大好处之一是 `getOrPut` 方法。 @@ -367,7 +367,7 @@ if (gop.found_existing) { 当然,只要不对哈希表进行修改,`value_ptr` 就应被视为有效。顺便提一句,这同样适用于我们通过 `iterator()`、`valueIterator` 和 `keyIterator` 获取的迭代键和值,原因相同。 -# 结论 +## 结论 希望你现在对使用「std.HashMap」、「std.AutoHashMap」和「std.StringHashMap」以及它们的「unmanaged」变体感到更加得心应手。虽然你可能永远不需要提供自己的上下文(例如「hash」和「eql」函数),但了解这是一个选项是有益的。在日常编程中,可视化数据尤其有用,尤其是在使用指针和添加间接层次时。每当我处理 `value_ptr` 或 `key_ptr` 时,我都会想到这些切片以及值或键与这些切片中值或键的实际地址之间的区别。 diff --git a/content/post/2024-08-12-zoop.smd b/content/post/2024-08-12-zoop.smd index cd4fd46..a13e854 100644 --- a/content/post/2024-08-12-zoop.smd +++ b/content/post/2024-08-12-zoop.smd @@ -6,17 +6,17 @@ .draft = false, --- -# zoop 是什么 +## [zoop]($section.id('zoop')) zoop 是 zig 的一个 OOP 解决方案,详细信息可以看看 [zoop官网](https://zhuyadong.github.io/zoop-docs/)。 -# 为什么不用别的 OOP 语言 +## 为什么不用别的 OOP 语言 简单的说,是我个人原因,必需使用 zig 的同时,还一定要用 OOP,所以有了 zoop。 -# zoop 入门 +## zoop 入门 -# 类和方法 +## 类和方法 ```zig pub const Base = struct { @@ -73,7 +73,7 @@ pub const Base = struct { 上面的代码给 `Base` 添加了一个可以继承的方法 `getName()`。 -# 类的继承 +## 类的继承 zoop 引入一个关键字 `extends` 用来实现继承,比如下面我们定义 `Base` 的子类 `Child`: @@ -102,7 +102,7 @@ test { } ``` -# 接口定义 +## 接口定义 zoop 中的接口,实际上是一个胖指针。下面我们定义一个接口 `IGetName`: @@ -132,7 +132,7 @@ pub const IGetName = struct { 上面的代码具体原理下面会说到,这里大家知道接口就是这样定义的就行了。上面的代码定义了接口 `IGetName`,这个接口有一个方法 `getName()`。 -# 接口实现 +## 接口实现 上面的 `Base` 类正好也有个符合 `IGetName` 接口的方法 `getName()`,那我们修改一下 `Base` 的代码让它来实现 `IGetName` 接口: @@ -148,7 +148,7 @@ pub const Base = struct { 可以看到实现接口和继承用的同样一个关键字 `extends`。因为子类会继承父类的接口,所以这样一来,`Child` 也自动实现了 `IGetName` 接口。 -# 方法重写和虚函数调用 +## 方法重写和虚函数调用 我们修改上面 `Child` 的代码,重写 `getName()` 方法: @@ -194,7 +194,7 @@ try t.expectEqualStrings(base.as(IGetName).?.getName(), "override"); 那么 zoop 的基本使用方法就介绍到这里,下面我们开始介绍 zoop 的实现原理。 -# 预设场景 +## 预设场景 接下来的讨论基于如下的属于 `mymod` 模块的类和接口: @@ -285,7 +285,7 @@ pub const Child = struct { - `Base`: 基类,实现接口 `ISetName` - `Child`: 子类,继承 `Base`,并实现接口 `IGetName` -# 核心数据结构 `zoop.Mixin(T)` +## 核心数据结构 `zoop.Mixin(T)` 我们看看两个类的 `mixin` 这个数据里面有什么: @@ -339,7 +339,7 @@ zoop.Mixin(Child) = struct { 上面两个函数获取的都是最外层对象的数据。根据对 `mixin` 数据的分析,zoop 的类型转换的原理就很清楚了,大家可以参考官网上关于 [类型转换](https://zhuyadong.github.io/zoop-docs/guide/as-cast) 的内容。 -# 动态构造类的方法、接口方法、和 `Vtable` +## 动态构造类的方法、接口方法、和 `Vtable` OOP 概念中的继承,重写,虚函数,实质其实就是在编译时动态构造需要的方法和属性。zoop 中主要是通过通过 [zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple) 这个模块来进行编译时动态构造。 @@ -347,11 +347,11 @@ OOP 概念中的继承,重写,虚函数,实质其实就是在编译时动 下面我介绍一下 zoop 中用到的 `comptime` 一些技巧,相信会对大家今后使用 zig 有帮助。 -# `struct` 很万能 +## `struct` 很万能 `comptime` 编程中,`struct` 是你最好的朋友,想在不同的 `comptime` 函数之间传递数据,最方便的方式,就是通过构造一个 `struct`,把想传递的数据通过 `pub const xxx = ...` 的方式传递出去,通过 `struct` 保存数据最好的地方,就在于这个数据在运行时也是可用的 (`struct` 中的常量,是保存在 exe 的 `.data` 区,运行时可见),[zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple) 就是通过这个方法实现的。 -# 动态构造 `struct` 的字段,用 `@Type()` +## 动态构造 `struct` 的字段,用 `@Type()` 网上好像很少有关于 `@Type()` 的使用说明,一般都是通过看 `zig.std` 的代码来学习,那我这里就稍微说明一下,希望能对大家有帮助。 目前 zig 通过 `@Type()`,能动态构造的 `struct`,只有纯字段类型的 `struct` (个人理解)。构造的方法,就是先把计算好的一个 `std.builtin.Type.StructField` 数组传递给 `@Type()` 来返回一个 `struct`,比如以下代码: @@ -398,7 +398,7 @@ const MyStruct = struct { zoop 动态构造 `Vtable` 就是通过这个方法做到的,参考 [zoop.DefVtable 原理](https://zhuyadong.github.io/zoop-docs/reference/principle#DefVtable) 和 [zoop 源代码](https://github.com/zhuyadong/zoop.git) -# 动态构造 `struct` 的函数,用 `usingnamespace` +## 动态构造 `struct` 的函数,用 `usingnamespace` 要想定义 `struct` 中的函数,理论上代码是一定要写在 `struct` 中的,目前 zig 唯一留下的一个口子,就是 `usingnamespace`,zoop 正是利用这个特性,来动态构造 `struct` 的函数。 @@ -442,7 +442,7 @@ pub usingnamespace struct { 因而实现了对 `Base.setName()` 方法的继承。 -# 运行时根据类型找 `Vtable` 和父类指针 +## 运行时根据类型找 `Vtable` 和父类指针 这个功能的实现当时第一版是使用的 `std.StaticStringMap` 保存了一个类中所有接口名到接口 `Vtable` ,以及父类名到父类数据在本类中的地址偏移的映射。和 C++ 的 `dynamic_cast` 比起来,性能是比较差的。后来看到西瓜大大发的一个链接 [点这里](https://github.com/SuperAuguste/cursed-zig-errors),忽然意识到这不就是我一直想要的 `comptime` 全局变量么,我终于能写出 `typeId(comptime T: type) u32` 这样的函数了: diff --git a/content/post/2024-11-26-typed-fsm.smd b/content/post/2024-11-26-typed-fsm.smd index 2241309..3d8eac9 100644 --- a/content/post/2024-11-26-typed-fsm.smd +++ b/content/post/2024-11-26-typed-fsm.smd @@ -6,9 +6,11 @@ .draft = false, --- -# 1. 简单介绍类型化有限状态机的优势 +## [typed fsm]($section.id('typed-fsm')) -# 1.1 介绍有限状态机 +## 1. 简单介绍类型化有限状态机的优势 + +## 1.1 介绍有限状态机 有限状态机(FSM,以下简称状态机)是程序中很常见的设计模式。 @@ -16,7 +18,7 @@ 而状态主要是在代码层面帮助人们理解消息的产生和处理。 -# 1.2 [typed-fsm-zig](https://github.com/sdzx-1/typed-fsm-zig) +## 1.2 [typed-fsm-zig](https://github.com/sdzx-1/typed-fsm-zig) typed-fsm-zig 是一个利用 zig 类型系统加一些编程规范实现的一个库,用于实现类型安全的有限状态机。 @@ -37,13 +39,13 @@ typed-fsm-zig 是一个利用 zig 类型系统加一些编程规范实现的一 在实际的使用中没有任何的代码生成,除了一处隐式的约束要求之外,没有任何其它的控制,开发者完全掌握状态机,因此你可以方便的将它和你现有的代码结合起来。 -# 2. 例子:修改 ATM 状态机的状态 +## 2. 例子:修改 ATM 状态机的状态 这里我将以一个 ATM 状态机(以下简称 ATM)的例子来展示 typed-fsm-zig 和 zig 的类型系统如何帮助我快速修改 ATM 的状态。 为了简单性,这里我不展示构建 ATM 这个例子的过程,感兴趣的可以在这里看到[代码](https://github.com/sdzx-1/typed-fsm-zig/blob/master/examples/atm-gui.zig)。 -# 2.1 介绍 ATM 状态机 +## 2.1 介绍 ATM 状态机 ATM 代表自动取款机,因此它的代码的逻辑就是模拟自动取款机的一些行为:插入银行卡,输入 pin,检查 pin,取钱,修改 pin。 @@ -74,7 +76,7 @@ ATM 代表自动取款机,因此它的代码的逻辑就是模拟自动取款 接下来的文章中我将修改 Update 的行为,并展示在这个过程中类型系统如何帮助我快速调整代码。 -# 2.2 修改 Update 消息 +## 2.2 修改 Update 消息 实际的消息 Update 定义代码如下 @@ -132,7 +134,7 @@ referenced by: 在这里类型系统精确的告诉了我们需要修改的地方,以及原因。修改完成后程序即能正确运行。 -# 2.3 移除 changePin 状态 +## 2.3 移除 changePin 状态 这一节中我们尝试移除 changePin 状态,看看类型系统会给我们什么反馈。 如果移除 changePin,新的状态图如下: @@ -175,7 +177,7 @@ examples/atm-gui.zig:296:10: error: no field named 'ChangePin' in enum '@typeInf 在这个过程中类型系统帮助我们找到问题和原因。这非常酷!!! -# 2.4 总结 +## 2.4 总结 以上是一个简单的例子,展示了 typed-fsm-zig 对于提升状态机编程体验的巨大效果。 @@ -192,7 +194,7 @@ examples/atm-gui.zig:296:10: error: no field named 'ChangePin' in enum '@typeInf -# 3. 原理与实现 +## 3. 原理与实现 最开始的版本是[typed-fsm](https://github.com/sdzx-1/typed-fsm),由使用 haskell 实现,它实现了完整类型安全的有限状态机。 @@ -432,7 +434,7 @@ fn s2Handler(val: Witness(Exmaple, .exit, .s2), ref: *i32) void { 以上就是 typed-fsm-zig 核心想法的完整介绍。接下来我将介绍需要的编程规范。 -# 4. typed-fsm-zig 需要哪些编程规范 +## 4. typed-fsm-zig 需要哪些编程规范 1. 状态和消息集合之间需要满足的隐式命名规范 @@ -546,7 +548,7 @@ pub fn readyHandler(comptime w: AtmSt.EWitness(.ready), ist: *InternalState) voi 遵循这四点要求,就能获得强大的类型安全保证,足以让你愉快的使用状态机! -# 5. 接下来能够增强的功能 +## 5. 接下来能够增强的功能 暂时我能想到的有如下几点: diff --git a/content/post/2025-01-23-bonkers-comptime.smd b/content/post/2025-01-23-bonkers-comptime.smd index a61fd52..d156dda 100644 --- a/content/post/2025-01-23-bonkers-comptime.smd +++ b/content/post/2025-01-23-bonkers-comptime.smd @@ -10,7 +10,7 @@ > 译注:原文中的代码块是交互式,翻译时并没有移植。另外,由于 comptime 本身即是关键概念,并且下文的意思更侧重于 Zig comptime 的特性,故下文大多使用 comptime 代替编译时概念。 -# 引子 +## 引子 编程通过自动化地处理数据极大地提升了生产力。而元编程则让我们可以像处理数据一样处理代码,以此将编程的力量反向作用于编程自身。而在底层编程中,我想元编程可能带来最大的优势,因为那些高级概念必须得精确映射到某些低级操作。然而,除了函数式编程语言外,我一直觉得各编程语言对元编程的实现并不理想。因此,当我看到 Zig 把元编程列为一个主要特性时,我提起了很大的兴趣。 @@ -20,7 +20,7 @@ 为了明确起见,所有示例都是有效的 Zig 代码,但示例中的转换只是概念性的,它们并不是 Zig 实际的实现方式。 -# 视角0: 忽略它 +## 视角0: 忽略它 我说我喜欢这个特性,却又立刻叫你忽略它,这确实有点怪。但我认为此处正是 Zig comptime 威力所体现的地方,所以我将从这里出发。Zig Zen 中的第三条是“倾向于阅读代码,而不是编写代码。”确实,能够轻松地阅读代码在各种情况下都很重要,因为它是建立概念理解的基础,而这种理解也是调试或修改代码所必需的。 @@ -71,7 +71,7 @@ pub fn main() void { Zig 中有很多基于 comptime 且远远不止这样简单的类型反射,但你只需要阅读那些代码、完全无需深入了解其中有关 comptime 的细节就可以理解它们在干什么。当然,如果你想使用 comptime 编写代码,则不能仅仅止步于此,让我们继续深入。 -# 视角1: 泛型 +## 视角1: 泛型 泛型在 Zig 中并不是一个特定的功能。相反,Zig 中的仅仅一小部分的 comptime 特性就可以提供用来处理你进行泛型编程所需的一切。这种视角虽然不能让你完全理解 comptime,但它确实为你提供了一个入口点,借此,你可以完成基于元编程的许多任务。 @@ -121,7 +121,7 @@ pub fn main() void { 当然,也可以通过使用特殊类型 anytype 来推断参数的类型,而这通常在参数的类型对函数签名的其余部分没有影响时使用。(译注:此时要限制 a, b, c 的类型相同,所以此处不用 anytype ) -# 视角2:编译时运行的标准代码 +## 视角2:编译时运行的标准代码 这是一个古老的故事: 增加一种自动执行命令的方法。当然,你还需要变量。 哦,还有条件。 拜托,能给我循环吗?这些看似合理的需求,最终导致这些自动化命令变得越来越复杂,甚至演变成一个完整的宏语言。 但 Zig 不同, 在运行时、编译时,甚至是构建系统中都使用了相同的语言。 @@ -178,7 +178,7 @@ pub fn main() !void { comptime 和运行时之间有一些小的区别。比如,只有 comptime 可以访问类型为 comptime_int、comptime_float 或 type 的变量。此外,一些函数只有 comptime 参数,这使它们仅限于编译时环境。相对的,只有运行时才能进行系统调用和那些依赖系统调用的函数。如果你的代码不使用这些特性,那么它在编译时和运行时中的表现将是一样的。 -# 视角3:[程序特化](https://en.wikipedia.org/wiki/Partial_application) +## 视角3:[程序特化](https://en.wikipedia.org/wiki/Partial_application) > 译者注:程序特化(Partial Evaluation)是一种编译优化技术,主要是:在编译期预先计算部分表达式或代码路径,以减少运行时计算开销,提前生成更具体的代码实现。 @@ -255,7 +255,7 @@ onst MyStruct = struct { 上面的示例是我们手动展开后的示例,但这项工作是由 Zig 的 comptime 完成的。这使得我们可以直接独立而完整地编写出我们要实现的功能,而不需要添加"当你改变 `MyStruct` 的字段时,记得更新 sum 函数"这样的由于依赖于 `MyStruct` 具体字段而预防功能失效的注释。 基于 comptime 的版本在 `MyStruct` 的任何字段变更时都可以正确地自动处理。 -# 视角4:Comptime 求值,运行时代码生成 +## 视角4:Comptime 求值,运行时代码生成 这与程序特化(Partial Evaluation)非常相似。这里有两个版本的代码,输入(编译前)和输出(编译后)。输入代码由编译器运行。如果一个语句在编译时是可知的,它就会被直接求值。但是如果一个语句需要某些运行时的值,那么这个语句就会被添加到输出代码中。 @@ -305,7 +305,7 @@ const MyStruct = struct { 这样做的另一个后果是,Zig 代码的静态分析要比大多数静态类型语言复杂得多,因为编译器需要运行很大一部分才能确定所有类型。 因此,在 Zig 工具链跟上之前,代码自动补全等编辑工具并不总是能很好地发挥作用。 -# 视角5:直接生成代码(Textual Code Generation) +## 视角5:直接生成代码(Textual Code Generation) 我在文章开头感叹元编程难度。然而,即使在 Zig 中,它仍然是一个强大的工具,在解决某些问题方面也占有一席之地。如果您熟悉这种元编程方法,对 Zig comptime 提供的功能可能会觉得有些残缺。比如, 怎么在写一段代码在运行时能够生成新代码? @@ -368,13 +368,13 @@ pub fn writeMyStructOfType( 与本节相关的是文本宏,如 C 语言中的文本宏。你可以做的大多数正常事情都可以在 comptime 中完成,尽管它们很少采用类似的形式。 不过,文本宏并不能做所有允许做的事情。 例如,你不能决定不喜欢某个 Zig 关键字,然后让宏代替你自己的关键字。 我认为这是一个正确的决定,尽管对于那些习惯了这种能力的人来说,这是一个艰难的过渡。 此外,Zig 参考了半个世纪以来的程序员在这方面的探索,所以它的选择要理智得多。 -# 结论 +## 结论 在阅读 Zig 代码以理解代码行为时,考虑 comptime 并不是必要的。而当编写 comptime 代码时,我通常会将其视为程序特化(Partial Evaluation)的一种形式。然而,如果你知道如何使用不同的元编程方法解决问题,你很可能有能力将其翻译成 comptime 形式。 元编程中直接生成代码的方法的存在,就是我全力支持 Zig 风格的 comptime 元编程的原因。尽管,直接生成代码是几乎是最强大的,但是,在阅读和调试时忽略 comptime 的特性的元编程方法确是最简单的。正因如此,我给本文取名为《Zig comptime 棒极了》。 -# 进一步阅读 +## 进一步阅读 Zig 并非一个仅仅依赖 comptime 这一特性的语言。你可以在[官方网站](https://ziglang.org/)上了解更多关于 Zig 的信息。 diff --git a/content/post/news/2023-12-11-first-meetup.smd b/content/post/news/2023-12-11-first-meetup.smd index fb6b87f..05a19e2 100644 --- a/content/post/news/2023-12-11-first-meetup.smd +++ b/content/post/news/2023-12-11-first-meetup.smd @@ -45,7 +45,7 @@ Zig,目前主要有以下几个: 我们希望通过这些努力,提高 Zig 语言的知名度,完善 Zig 语言的生态,促进 Zig 语言的交流和学习。 -# 结论 +## 结论 Zig 中文社区第一次线上会议的召开,标志着 Zig 社区正式启航。如果读者对共建社区感兴趣,欢迎与我们联系。 diff --git a/content/post/news/2023-12-27-second-meetup.smd b/content/post/news/2023-12-27-second-meetup.smd index 71cf0dd..22826e7 100644 --- a/content/post/news/2023-12-27-second-meetup.smd +++ b/content/post/news/2023-12-27-second-meetup.smd @@ -18,40 +18,40 @@ 这次会议主要是同步了之前会议落实的 action,主要是同步了不同项目的进展,由于临近年底,大家进度都不算太大,但还是有所进展,算是开了个好头😄 -# 项目进展 +## 项目进展 -# [Zig-OS](https://github.com/zigcc/zig-os) +## [Zig-OS](https://github.com/zigcc/zig-os) - 主要参与人员:西瓜 - 进展:粗略看完 rust 版本的教程;完成 freestanding 二进制,现在卡在了 bootloader 阶段 -# [Learn zig](https://github.com/learnzig/learnzig) +## [Learn zig](https://github.com/learnzig/learnzig) - 主要参与人员:金中甲 - zig的进阶特性,诸如构建系统、包管理、与C交互均已完成,目前教程内容已基本覆盖日常使用 - 增加了评论区的功能 - 待完成:反射(编译期反射和运行时反射)、内建函数说明(包含使用例子)、未定义行为、wasm、原子操作这些边缘部分 -# Zig 教学视频 +## Zig 教学视频 - 主要参与人员:Lambert - - 暂无明显进展 -# [Zig cookbook](https://github.com/zigcc/zig-cookbook) +## [Zig cookbook](https://github.com/zigcc/zig-cookbook) - 主要参与人员:夜白、西瓜 - 已经完成大部分内容 👍 -# Zig 构建系统教程 +## Zig 构建系统教程 - 主要参与人员:Reco - 目前主要是对 [zig build explained](https://zig.news/xq/zig-build-explained-part-3-1ima) 系列文章翻译 -# 新人介绍 +## 新人介绍 在第一次会议后,有一些朋友想加入 ZigCC 社区,经过简单筛选,新增一名成员:Reco,下面是他的一些履历: diff --git a/content/post/news/2024-01-14-third-meetup.smd b/content/post/news/2024-01-14-third-meetup.smd index aa903d2..299630d 100644 --- a/content/post/news/2024-01-14-third-meetup.smd +++ b/content/post/news/2024-01-14-third-meetup.smd @@ -18,7 +18,7 @@ - 公众号运营 - 如何与其他社区互动 -# 公众号运营 +## 公众号运营 这是最近群里聊到的问题,由于 Zig 语言本身属于较新的技术,因此社区内资料比较少,这导致很多感兴趣的人没有一个好的学习途径。 @@ -40,12 +40,12 @@ 主要参与人员:西瓜、金中甲 -# 社区互动 +## 社区互动 目前我们的成员在 Zig 的实践方面相对较少,因此决定目前不过多的去宣传,在积攒了一些实际项目经验后,再来考虑。 -# 欢迎更多朋友加入 ZigCC +## 欢迎更多朋友加入 ZigCC 现在回看,距离第一次 ZigCC 线上会议过了一个月,经过 ZigCC 成员的努力,还是交出了一份比较满意的答卷,[cookbook](https://github.com/zigcc/zig-cookbook) diff --git a/fix_zig_update_section.js b/fix_zig_update_section.js deleted file mode 100644 index 52caa1f..0000000 --- a/fix_zig_update_section.js +++ /dev/null @@ -1,43 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const MONTHLY_DIR = path.join(__dirname, 'content', 'monthly'); - -// 更宽松地匹配 Zig 语言更新 section 标题(允许不同空格、section id、大小写) -const zigUpdateSectionRegex = /^(#+) \[Zig[  ]*语言更新\]\(\$section\.id\(['"]?[^'")]+['"]?\)\)[\s\S]*?(?=^#|\n{2,}|\n*$)/gim; -// 匹配 github 链接和日期区间 -const githubLinkRegex = /(https:\/\/github\.com\/ziglang\/zig\/pulls\?[^)]*closed%3A(\d{4}-\d{2}-\d{2})\.\.(\d{4}-\d{2}-\d{2}))/i; - -function fixZigUpdateSection(content) { - // 找到所有 Zig 语言更新 section - return content.replace(zigUpdateSectionRegex, (sectionBlock, hashes) => { - // 在 sectionBlock 内查找 github 链接 - const linkMatch = sectionBlock.match(githubLinkRegex); - if (!linkMatch) return sectionBlock; // 没有链接则不处理 - const url = linkMatch[1]; - const date1 = linkMatch[2]; - const date2 = linkMatch[3]; - // 构造新格式 - return `${hashes} [Zig 语言更新]($section.id('zig-update'))\n[${date1}..${date2}](${url})`; - }); -} - -function processFile(filePath) { - let content = fs.readFileSync(filePath, 'utf8'); - const newContent = fixZigUpdateSection(content); - if (newContent !== content) { - fs.writeFileSync(filePath, newContent, 'utf8'); - console.log('Fixed Zig 语言更新 in:', path.basename(filePath)); - } -} - -function main() { - const files = fs.readdirSync(MONTHLY_DIR).filter(f => f.endsWith('.smd')); - for (const file of files) { - processFile(path.join(MONTHLY_DIR, file)); - } -} - -if (require.main === module) { - main(); -} \ No newline at end of file From 36ecac86e3275578e6b62a4e8817b50cda3fe77b Mon Sep 17 00:00:00 2001 From: xihale Date: Wed, 2 Jul 2025 08:32:04 +0800 Subject: [PATCH 14/22] Standardize section titles across multiple SMD files to improve content structure and readability. Update headings to include links for better navigation and consistency throughout the documentation. --- content/about.smd | 4 +-- content/community.smd | 2 +- content/contributing.smd | 6 ++-- content/learn/02-installing-zig.smd | 2 +- content/learn/07-stack-memory.smd | 4 +-- content/learn/08-heap-memory.smd | 16 +++++----- content/learn/index.smd | 8 ++--- content/monthly/202207.smd | 4 +-- content/monthly/202208.smd | 4 +-- content/monthly/202209.smd | 10 +++---- content/monthly/202210.smd | 4 +-- content/monthly/202211.smd | 6 ++-- content/monthly/202212.smd | 4 +-- content/monthly/202301.smd | 4 +-- content/monthly/202302.smd | 6 ++-- content/monthly/202303.smd | 6 ++-- content/monthly/202304.smd | 6 ++-- content/monthly/202305.smd | 6 ++-- content/monthly/202306.smd | 8 ++--- content/monthly/202307.smd | 6 ++-- content/monthly/202308.smd | 28 ++++++++--------- content/monthly/202309.smd | 6 ++-- content/monthly/202310.smd | 6 ++-- content/monthly/202311.smd | 6 ++-- content/monthly/202402.smd | 6 ++-- content/monthly/202403.smd | 6 ++-- content/monthly/202404.smd | 6 ++-- content/monthly/202405.smd | 4 +-- content/monthly/202406.smd | 6 ++-- content/monthly/202407.smd | 6 ++-- content/monthly/202410.smd | 10 +++---- content/monthly/202411.smd | 4 +-- content/post/2023-09-05-hello-world.smd | 4 +-- content/post/2023-09-21-zig-midi.smd | 24 +++++++-------- .../2023-12-24-zig-build-explained-part1.smd | 16 +++++----- .../2023-12-28-zig-build-explained-part2.smd | 30 +++++++++---------- .../2023-12-29-zig-build-explained-part3.smd | 30 +++++++++---------- ...2-how-to-release-your-zig-applications.smd | 14 ++++----- content/post/2024-04-06-zig-cpp.smd | 8 ++--- content/post/2024-05-07-package-hash.smd | 20 ++++++------- content/post/2024-05-24-interface-idioms.smd | 14 ++++----- content/post/2024-06-10-zig-hashmap-1.smd | 8 ++--- content/post/2024-06-11-zig-hashmap-2.smd | 8 ++--- content/post/2024-08-12-zoop.smd | 28 ++++++++--------- content/post/2024-11-26-typed-fsm.smd | 22 +++++++------- content/post/2025-01-23-bonkers-comptime.smd | 18 +++++------ content/post/news/2023-12-11-first-meetup.smd | 2 +- .../post/news/2023-12-27-second-meetup.smd | 8 ++--- content/post/news/2024-01-14-third-meetup.smd | 6 ++-- 49 files changed, 235 insertions(+), 235 deletions(-) diff --git a/content/about.smd b/content/about.smd index 3642810..ddcea50 100644 --- a/content/about.smd +++ b/content/about.smd @@ -6,7 +6,7 @@ .draft = false, --- -## About Zine +## [About Zine]($section.id('About Zine')) Zine is an MIT-licensed project created by [Loris Cro](https://kristoff.it) and other contributors listed on the [official repository](https://github.com/kristoff-it/zine/contributors). @@ -34,7 +34,7 @@ of authoring languages: ># [NOTE]($block) >The correct file extension for SuperMD pages is `.smd`. -## Zine is alpha software +## [Zine is alpha software]($section.id('Zine is alpha software')) Zine is not yet complete. The main functionality is present and you will be able to build even moderately complex static websites without issue. diff --git a/content/community.smd b/content/community.smd index 9ce6ae6..3c6c83f 100644 --- a/content/community.smd +++ b/content/community.smd @@ -8,7 +8,7 @@ TODO: issue [让我们一起探索 Zig 的魅力,推动 Zig 在中文社区内的发展!]($text.attrs('center','large','bold')) -## 网站更新日志 +## [网站更新日志]($section.id('网站更新日志')) - **2025-06-30:** 切换到 [zine](https://zine-ssg.io/) - **2024-08-18:** 切换主题 [docsy](https://github.com/google/docsy) diff --git a/content/contributing.smd b/content/contributing.smd index b0fec1e..2d15fc2 100644 --- a/content/contributing.smd +++ b/content/contributing.smd @@ -12,7 +12,7 @@ Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文 2. 改进 zigcc 组织下的开源项目,这是 [open issues](https://github.com/search?q=state%3Aopen+org%3Azigcc++NOT+%E6%97%A5%E6%8A%A5&type=issues&ref=advsearch) 3. 参与不定期的线上会议 TODO -## 供稿方式 +## [供稿方式]($section.id('供稿方式')) 1. Fork 仓库 https://github.com/zigcc/zigcc.github.io 2. 在 `content/post` 内添加自己的文章(md 或 org 格式均可),文件命名为: `${YYYY}-${MM}-${DD}-${SLUG}.md` @@ -26,13 +26,13 @@ date: '2023-09-05T16:13:13+0800' --- ``` -## 本地预览 +## [本地预览]($section.id('本地预览')) TODO ```bash zine ``` -## 发布平台 +## [发布平台]($section.id('发布平台')) - [ZigCC 网站](https://ziglang.cc) - [ZigCC 公众号](https://github.com/zigcc/.github/raw/main/zig_mp.png) diff --git a/content/learn/02-installing-zig.smd b/content/learn/02-installing-zig.smd index 50879fc..9e0a706 100644 --- a/content/learn/02-installing-zig.smd +++ b/content/learn/02-installing-zig.smd @@ -25,7 +25,7 @@ EOF asdf plugin-add zig https://github.com/zigcc/asdf-zig.git -## 安装最新版 +## [安装最新版]($section.id('安装最新版')) asdf install zig latest asdf global zig latest zig version diff --git a/content/learn/07-stack-memory.smd b/content/learn/07-stack-memory.smd index 54e43dc..9b7db99 100644 --- a/content/learn/07-stack-memory.smd +++ b/content/learn/07-stack-memory.smd @@ -18,7 +18,7 @@ > 三块内存区域实际上没有真正的物理差别。操作系统和可执行文件创造了"内存区域"这个概念。 -## 栈帧 +## [栈帧]($section.id('栈帧')) 迄今为止,我们所见的所有数据都是常量,存储在二进制的全局数据部分或作为局部变量。局部表示该变量只在其声明的范围内有效。在 Zig 中,范围从花括号开始到结束。大多数变量的范围限定在一个函数内,包括函数参数,或一个控制流块,比如 if。但是,正如所见,你可以创建任意块,从而创建任意范围。 @@ -92,7 +92,7 @@ main: user -> ------------- (id: 1043368d0) 与全局数据一样,调用栈也由操作系统和可执行文件管理。程序启动时,以及此后启动的每个线程,都会创建一个调用栈(其大小通常可在操作系统中配置)。调用栈在程序的整个生命周期中都存在,如果是线程,则在线程的整个生命周期中都存在。程序或线程退出时,调用栈将被释放。我们的全局数据包含所有程序的全局数据,而调用栈只包含当前执行的函数层次的栈帧。这样做既能有效利用内存,又能简化堆栈帧的管理。 -## 悬空指针 +## [悬空指针]($section.id('悬空指针')) 栈帧的简洁和高效令人惊叹。但它也很危险:当函数返回时,它的任何本地数据都将无法访问。这听起来似乎很合理,毕竟这是本地数据,但却会带来严重的问题。请看这段代码: diff --git a/content/learn/08-heap-memory.smd b/content/learn/08-heap-memory.smd index 05ea3f0..6a23bc3 100644 --- a/content/learn/08-heap-memory.smd +++ b/content/learn/08-heap-memory.smd @@ -52,7 +52,7 @@ fn getRandomCount() !u8 { 一般来说,每次 `alloc` 都会有相应的 `free`。`alloc`分配内存,`free`释放内存。不要让这段简单的代码限制了你的想象力。这种 `try alloc` + `defer free` 的模式很常见,这是有原因的:在我们分配内存的地方附近释放相对来说是万无一失的。但同样常见的是在一个地方分配,而在另一个地方释放。正如我们之前所说,堆没有内置的生命周期管理。你可以在 HTTP 处理程序中分配内存,然后在后台线程中释放,这是代码中两个完全独立的部分。 -## defer 和 errdefer +## [defer 和 errdefer]($section.id('defer 和 errdefer')) 说句题外话,上面的代码介绍了一个新的语言特性:`defer`,它在退出作用域时执行给定的代码。『作用域退出』包括到达作用域的结尾或从作用域返回。严格来说, `defer` 与分配器或内存管理并无严格关系;你可以用它来执行任何代码。但上述用法很常见。 @@ -100,7 +100,7 @@ pub const Game = struct { > `init` 和 `deinit` 的名字并不特殊。它们只是 Zig 标准库使用的,也是社区采纳的名称。在某些情况下,包括在标准库中,会使用 `open` 和 `close`,或其他更适当的名称。 -## 双重释放和内存泄漏 +## [双重释放和内存泄漏]($section.id('双重释放和内存泄漏')) 上面提到过,没有规则规定什么时候必须释放什么东西。但事实并非如此,还是有一些重要规则,只是它们不是强制的,需要你自己格外小心。 @@ -159,7 +159,7 @@ fn isSpecial(allocator: Allocator, name: [] const u8) !bool { 至少在双重释放的情况下,我们的程序会遭遇严重崩溃。内存泄漏可能很隐蔽。不仅仅是根本原因难以确定。真正的小泄漏或不常执行的代码中的泄漏甚至很难被发现。这是一个很常见的问题,Zig 提供了帮助,我们将在讨论分配器时看到。 -## 创建与销毁 +## [创建与销毁]($section.id('创建与销毁')) `std.mem.Allocator`的`alloc`方法会返回一个切片,其长度为传递的第二个参数。如果想要单个值,可以使用 `create` 和 `destroy` 而不是 `alloc` 和 `free`。 @@ -233,7 +233,7 @@ fn init(allocator: std.mem.Allocator, id: u64, power: i32) !*User{ 请记住,`create` 返回一个 `!*User`,所以我们的 `user` 是 `*User` 类型。 -## 分配器 Allocator +## [分配器 Allocator]($section.id('分配器 Allocator')) Zig 的核心原则之一是无隐藏内存分配。根据你的背景,这听起来可能并不特别。但这与 C 语言中使用标准库的 malloc 函数分配内存的做法形成了鲜明的对比。在 C 语言中,如果你想知道一个函数是否分配内存,你需要阅读源代码并查找对 malloc 的调用。 @@ -254,7 +254,7 @@ defer allocator.free(say); 如果你正在构建一个库,那么最好接受一个 `std.mem.Allocator`,然后让库的用户决定使用哪种分配器实现。否则,你就需要选择正确的分配器,正如我们将看到的,这些分配器并不相互排斥。在你的程序中创建不同的分配器可能有很好的理由。 -## 通用分配器 GeneralPurposeAllocator +## [通用分配器 GeneralPurposeAllocator]($section.id('通用分配器 GeneralPurposeAllocator')) 顾名思义,`std.heap.GeneralPurposeAllocator` 是一种通用的、线程安全的分配器,可以作为应用程序的主分配器。对于许多程序来说,这是唯一需要的分配器。程序启动时,会创建一个分配器并传递给需要它的函数。我的 HTTP 服务器库中的示例代码就是一个很好的例子: @@ -299,7 +299,7 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 类型是什么,字段在哪里?类型其实是 `std.heap.general_purpose_allocator.Config`,但它并没有直接暴露出来,这也是我们没有显式给出类型的原因之一。没有设置字段是因为 Config 结构定义了默认值,我们将使用默认值。这是配置、选项的中常见的模式。事实上,我们在下面几行向 `init` 传递 `.{.port = 5882}` 时又看到了这种情况。在本例中,除了端口这一个字段外,我们都使用了默认值。 -## std.testing.allocator +## [std.testing.allocator]($section.id('std.testing.allocator')) 希望当我们谈到内存泄漏时,你已经足够烦恼,而当我提到 Zig 可以提供帮助时,你肯定渴望了解更多这方面内容。这种帮助来自 `std.testing.allocator`,它是一个 `std.mem.Allocator` 实现。目前,它基于通用分配器(GeneralPurposeAllocator)实现,并与 Zig 的测试运行器进行了集成,但这只是实现细节。重要的是,如果我们在测试中使用 `std.testing.allocator`,就能捕捉到大部分内存泄漏。 @@ -423,7 +423,7 @@ self.allocator.free(self.items); 将`items`复制到我们的 `larger` 切片中后, 添加最后一行`free`可以解决泄漏的问题。如果运行 `zig test learning.zig`,便不会再有错误。 -## ArenaAllocator +## [ArenaAllocator]($section.id('ArenaAllocator')) 通用分配器(GeneralPurposeAllocator)是一个合理的默认设置,因为它在所有可能的情况下都能很好地工作。但在程序中,你可能会遇到一些固定场景,使用更专业的分配器可能会更合适。其中一个例子就是需要在处理完成后丢弃的短期状态。解析器(Parser)通常就有这样的需求。一个 `parse` 函数的基本轮廓可能是这样的 @@ -508,7 +508,7 @@ defer list.deinit(); 最后举个简单的例子,我上面提到的 HTTP 服务器在响应中暴露了一个 `ArenaAllocator`。一旦发送了响应,它就会被清空。由于`ArenaAllocator`的生命周期可以预测(从请求开始到请求结束),因此它是一种高效的选择。就性能和易用性而言,它都是高效的。 -## 固定缓冲区分配器 FixedBufferAllocator +## [固定缓冲区分配器 FixedBufferAllocator]($section.id('固定缓冲区分配器 FixedBufferAllocator')) 我们要讨论的最后一个分配器是 `std.heap.FixedBufferAllocator`,它可以从我们提供的缓冲区(即 `[]u8`)中分配内存。这种分配器有两大好处。首先,由于所有可能使用的内存都是预先创建的,因此速度很快。其次,它自然而然地限制了可分配内存的数量。这一硬性限制也可以看作是一个缺点。另一个缺点是,`free` 和 `destroy` 只对最后分配/创建的项目有效(想想堆栈)。调用释放非最后分配的内存是安全的,但不会有任何作用。 diff --git a/content/learn/index.smd b/content/learn/index.smd index e73fe53..b4ce493 100644 --- a/content/learn/index.smd +++ b/content/learn/index.smd @@ -24,7 +24,7 @@ 初次接触 Zig 的用户可以按序号依次阅读,对于有经验的 Zig 开发者可按需阅读感兴趣的章节。 -## 关于原作者 +## [关于原作者]($section.id('关于原作者')) [Karl Seguin](https://www.linkedin.com/in/karlseguin/) 在多个领域有着丰富经验,前微软 MVP,他撰写了大量文章,是多个微软公共新闻组的活跃成员。现居新加坡。他还是以下教程的作者: @@ -34,13 +34,13 @@ 可以在 找到他的博客,或者通过 [@karlseguin](http://twitter.com/karlseguin) 在 Twitter 上关注他。 -## 翻译原则 +## [翻译原则]($section.id('翻译原则')) 技术文档的翻译首要原则是准确,但在准确的前提下如何保证『信、达、雅』?这是个挑战,在翻译本教程时,在某些情况下会根据上下文进行意译,便于中文读者阅读。 最后,感谢翻译者的无私贡献。❤️️ -## 离线阅读 +## [离线阅读]($section.id('离线阅读')) 在本仓库的 [release 页面](https://github.com/zigcc/zigcc.github.io/releases)会定期将本教程导出为 PDF 格式,读者可按需下载。 @@ -49,7 +49,7 @@ ``` -## 其他学习资料 +## [其他学习资料]($section.id('其他学习资料')) 由于 Zig 目前还处于快速迭代,因此最权威的资料无疑是官方的 [Zig Language Reference](https://ziglang.org/documentation/master/),遇到语言的细节问题,基本都可以在这里找到答案。 其次是社区的一些高质量教程,例如: diff --git a/content/monthly/202207.smd b/content/monthly/202207.smd index eaa0323..2cdd993 100644 --- a/content/monthly/202207.smd +++ b/content/monthly/202207.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) - [Zig 初体验 - Keep Coding](https://liujiacai.net/blog/2022/07/16/zig-intro/) @@ -28,7 +28,7 @@ before starting with Zig? : Zig](https://www.reddit.com/r/Zig/comments/w63x6r/is_it_necessary_to_know_c_or_another_systems/) -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) - [Release bun v0.1.5 · oven-sh/bun](https://github.com/oven-sh/bun/releases/tag/bun-v0.1.5) diff --git a/content/monthly/202208.smd b/content/monthly/202208.smd index 11bdf55..d9ccb1c 100644 --- a/content/monthly/202208.smd +++ b/content/monthly/202208.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) - [Growing a {{mustache}} with Zig](https://zig.news/batiati/growing-a-mustache-with-zig-di4) @@ -29,7 +29,7 @@ devlog](https://devlog.hexops.com/2022/packed-structs-in-zig/)。使用 Zig 的 `packet struct` 实现 bit set 功能 -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) - [Virtual tables by vrischmann · Pull Request \#100 · vrischmann/zig-sqlite](https://github.com/vrischmann/zig-sqlite/pull/100)。zig-sqlite diff --git a/content/monthly/202209.smd b/content/monthly/202209.smd index 92caedd..8a56507 100644 --- a/content/monthly/202209.smd +++ b/content/monthly/202209.smd @@ -6,7 +6,7 @@ .draft = false, --- -## Zig VS Rust 火花 +## [Zig VS Rust 火花]($section.id('Zig VS Rust 火花')) 在 9/10 号左右,在 Twitter 上牵起了一小波关于 Zig VS Rust 的小火花,以至于最后 Zig 创始人 Andrew Kelley @@ -16,7 +16,7 @@ Let us exist。这里稍微整理下这件事情的过程: 本次事件主要 - Rust 核心贡献者: Patrick Walton - Zig 社区 VP: Loris Cro -### 时间线 +### [时间线]($section.id('时间线')) - 8/26 号,一篇关于 wasm 2 Game Jam 的[分析报告](https://wasm4.org/blog/jam-2-results/)中,使用 Zig @@ -57,7 +57,7 @@ Let us exist。这里稍微整理下这件事情的过程: 本次事件主要 这里[提到的](https://github.com/tigerbeetledb/tigerbeetle/blob/main/docs/DESIGN.md)。而且即便内存安全,也可能发生 OOM -### 总结 +### [总结]($section.id('总结')) 上面的链接比较多,这里稍微总结下这次争论的问题: @@ -69,7 +69,7 @@ Zig,笔者觉得大概率就是本次争论的观点,Rust 过于复杂,导致程序员不仅仅要考虑业务行为,还需要按照 Rust 的风格来编程,这加剧了程序出错的可能性。 -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) - [How (memory) safe is zig?](https://www.scattered-thoughts.net/writing/how-safe-is-zig/) @@ -102,7 +102,7 @@ Zig,笔者觉得大概率就是本次争论的观点,Rust - [Zig ⚡ Improving the User Experience for Unused Variables](https://vimeo.com/748218307) -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) - [Zig 开发常用类库](https://github.com/zigcc/forum/discussions/28),如果读者的 diff --git a/content/monthly/202210.smd b/content/monthly/202210.smd index 8acd311..b0f0673 100644 --- a/content/monthly/202210.smd +++ b/content/monthly/202210.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) - [Zig Is Self-Hosted Now, What’s Next? | Loris Cro’s Blog](https://kristoff.it/blog/zig-self-hosted-now-what/) | 0.10 @@ -39,7 +39,7 @@ - \[音频\] [005. 与 LemonHX 畅聊新一代编程语言 Zig – RustTalk](https://rusttalk.github.io/podcast/005/) -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) - [Himujjal/zig-json5](https://github.com/Himujjal/zig-json5): A JSON5 parser/stringifier for Zig resembling the std.json API diff --git a/content/monthly/202211.smd b/content/monthly/202211.smd index 0c18211..54c342e 100644 --- a/content/monthly/202211.smd +++ b/content/monthly/202211.smd @@ -17,13 +17,13 @@ compiler,也称为『自举』,即可以用 Zig 来写 Zig 赶紧升级吧,少年! -## zigcc 中文社区微信群 +## [zigcc 中文社区微信群]($section.id('zigcc 中文社区微信群')) 欢迎喜欢 Zig 的小伙伴加入! {{\< figure src=“https://github.com/zigcc/.github/raw/main/weixin.jpg” width=“200” title=“ZigCC 微信群二维码” \>}} -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) - [Wasmer 3.0 使用 Zig 进行跨平台编译 · Discussion \#35 · zigcc/forum](https://github.com/zigcc/forum/discussions/35) @@ -98,7 +98,7 @@ const Animal = union(enum){ [spoon](https://sr.ht/~leon_plickat/zig-spoon/) 这个库开发了一个帮助自己进行图片打标的 UI 工具, -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) - [Zig 程序设计语言中文手册](https://sxwangzhiwen.github.io/zigcndoc/zigcndoc.html) diff --git a/content/monthly/202212.smd b/content/monthly/202212.smd index 7a32c91..755bf47 100644 --- a/content/monthly/202212.smd +++ b/content/monthly/202212.smd @@ -6,11 +6,11 @@ .draft = false, --- -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) - [Goodbye to the C++ Implementation of Zig ⚡ Zig Programming Language](https://ziglang.org/news/goodbye-cpp/) -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) ## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-12-01..2023-01-01) diff --git a/content/monthly/202301.smd b/content/monthly/202301.smd index 3cb3124..b4f0a1e 100644 --- a/content/monthly/202301.smd +++ b/content/monthly/202301.smd @@ -59,7 +59,7 @@ pub fn build(b: *std.build.Builder) void { [15.0.7](http://releases.llvm.org/15.0.7/docs/ReleaseNotes.html) - 是 0.10.x 的最后一个 release 版本 -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) [Code study: interface idioms/patterns in zig standard libraries](https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj) @@ -77,7 +77,7 @@ Beetle](https://datastackshow.com/podcast/why-accounting-needs-its-own-database- Zig](https://0110.be/posts/Crossplatform_JNI_builds_with_Zig) 又一个使用 Zig 作为交叉编译的例子 -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) [Introducing ⚡zap⚡ - blazingly fast backends in zig](https://zig.news/renerocksai/introducing-zap-blazingly-fast-backends-in-zig-3jhh) diff --git a/content/monthly/202302.smd b/content/monthly/202302.smd index 33acb29..4afce6c 100644 --- a/content/monthly/202302.smd +++ b/content/monthly/202302.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 包管理器进展 +## [包管理器进展]($section.id('包管理器进展')) 包管理器自 [\#14265](https://github.com/ziglang/zig/pull/14265) 合并后一直在不断推进,以下两个是最主要的改变: @@ -61,7 +61,7 @@ 也欢迎大家给自己熟悉的 C/C++ 项目提 PR 让其支持 zig build,让构建不再那么痛苦。 -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) - [How a Zig IDE Could Work](https://matklad.github.io/2023/02/10/how-a-zig-ide-could-work.html) @@ -105,7 +105,7 @@ build,让构建不再那么痛苦。 - [Smoking Hot Binary Search In Zig](https://blog.deckc.hair/2023-02-22-smoking-hot-binary-search-in-zig.html) -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) - [Writing high-performance clients for TigerBeetle](https://tigerbeetle.com/blog/2023-02-21-writing-high-performance-clients-for-tigerbeetle/) diff --git a/content/monthly/202303.smd b/content/monthly/202303.smd index 7c9af36..53a58d9 100644 --- a/content/monthly/202303.smd +++ b/content/monthly/202303.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) [Creating arbitrary error values by using error.Something syntax](https://www.reddit.com/r/zig/comments/11wmoky) @@ -61,7 +61,7 @@ Nix](https://flyx.org/cross-packaging/) [Zig And Rust](https://matklad.github.io/2023/03/26/zig-and-rust.html) -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) [macovedj/doink](https://github.com/macovedj/doink) Making WebAssembly Components with Zig @@ -98,7 +98,7 @@ bug,一个影响比较大的是 test 的构建方式不一样了。 现在 `addTest` 和 `addExecutable` 一样,输出都是 `CompileStep` ,它默认不会执行,需要调用 `run()` 拿到 run step 才可以。 -## Zig 构建系统介绍 +## [Zig 构建系统介绍]($section.id('Zig 构建系统介绍')) 构建系统其实是 Zig 生态中比较重要的一环,和 Rust 不同,Zig 即是一门编程语言,也是一系列工具链,比如编译 Zig 时,没有 zigc diff --git a/content/monthly/202304.smd b/content/monthly/202304.smd index a933692..9749e31 100644 --- a/content/monthly/202304.smd +++ b/content/monthly/202304.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 重大事件 +## [重大事件]($section.id('重大事件')) 在 2023 四月份的 [Tiobe](https://www.tiobe.com/tiobe-index/) 指数上,Zig [排名 @@ -29,7 +29,7 @@ Loris 发推表示这个数字对 Zig \> -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) [When should I use an UNTAGGED Union?](https://zig.news/kristoff/when-should-i-use-an-untagged-union-56ek) @@ -78,7 +78,7 @@ Systems Distributed 23 视频,[B 站链接](https://www.bilibili.com/video/BV1gP41117zY/),作者博客:[Scattered Thoughts](https://www.scattered-thoughts.net/) -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) [Coming Soon to a Zig Near You: HTTP Client](https://zig.news/nameless/coming-soon-to-a-zig-near-you-http-client-5b81) diff --git a/content/monthly/202305.smd b/content/monthly/202305.smd index 99858fc..8a0919e 100644 --- a/content/monthly/202305.smd +++ b/content/monthly/202305.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 重大事件 +## [重大事件]($section.id('重大事件')) 这个月主要的事情就是 HTTP server 在标准库的增加了,具体可参考: @@ -15,7 +15,7 @@ - [http server in the standard library · Issue \#910](https://github.com/ziglang/zig/issues/910) -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) [Integrating Zig and SwiftUI](https://mitchellh.com/writing/zig-and-swiftui) @@ -67,7 +67,7 @@ System](https://www.priver.dev/blog/zig/initial-commit-build-system/) [Writing DNS resolver in Zig](https://e-aakash.github.io/update/2023/06/04/resolv-dns-resolver-in-zig.html) -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) [Zig by Example](https://zigbyexample.github.io/) 非常好的学习资料。Learn How to use Zig’s Standard Library, by small diff --git a/content/monthly/202306.smd b/content/monthly/202306.smd index 6960230..4646b5c 100644 --- a/content/monthly/202306.smd +++ b/content/monthly/202306.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 重大事件 +## [重大事件]($section.id('重大事件')) 一个是这个:[The Zig subreddit has closed](https://ziggit.dev/t/the-zig-subreddit-has-closed/679),现在 @@ -91,7 +91,7 @@ Zig 的各种 backend 进展,能不能给 fix 几个 regression?! 最后,即便 Zig 这个项目夭折了,我相信通过这个学习的过程也有助于提高我们对系统编程、编译器的理解,不是吗? -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) [A Note About Zig Books for the Zig Community](https://kristoff.it/blog/note-about-zig-books/) @@ -147,14 +147,14 @@ segfaults](https://www.openmymind.net/Zig-Danling-Pointers/) 讨论](https://news.ycombinator.com/item?id=36149462) -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) [pondzdev/duckdb](https://github.com/pondzdev/duckdb-proxy/) 一个将 DuckDB 数据库通过 HTTP API 暴露出来的代理,主要是利用了 [DuckDB C API](https://duckdb.org/docs/api/c/api.html),示例: ``` bash -## open the database in readonly (DB must exist in this case) +## [open the database in readonly (DB must exist in this case)]($section.id('open the database in readonly (DB must exist in this case)')) $ ./duckdb-proxy --readonly db/mydatabase.duckdb $ curl http://localhost:8012/api/1/exec \ diff --git a/content/monthly/202307.smd b/content/monthly/202307.smd index b1d3ac9..7abedcb 100644 --- a/content/monthly/202307.smd +++ b/content/monthly/202307.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 重大事件 +## [重大事件]($section.id('重大事件')) Andrewk 在最新的文章 [The Upcoming Release Postponed Two More Weeks and Lacks Async Functions](https://ziglang.org/news/0.11.0-postponed-again/) @@ -24,7 +24,7 @@ Team](https://ziglang.org/news/welcome-jacob-young/),Core Team 恭喜 Core Team,又添一虎将! -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) [Copy Hunting | TigerBeetle](https://tigerbeetle.com/blog/2023-07-26-copy-hunting/) 比较有意思的文章,通过分析 LLVM 的 IR,来避免程序中不必要的 memcpy @@ -129,7 +129,7 @@ TigerBeetle 的新花样,把数据库搬到了 Web 上。[HN 比较有意思的文章,作者比较了 Clang、Zig 对相同逻辑代码生产的 LLVM IR,Zig 生成的 IR 更加精简 -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) [Zig helped us move data to the Edge. Here are our impressions](https://blog.turso.tech/zig-helped-us-move-data-to-the-edge-here-are-our-impressions-67d3a9c45af4) [Turso](https://turso.tech/) diff --git a/content/monthly/202308.smd b/content/monthly/202308.smd index bff5532..2a3c353 100644 --- a/content/monthly/202308.smd +++ b/content/monthly/202308.smd @@ -11,7 +11,7 @@ 0.11 终于在 8 月 4 号释出了,下面来看看它的一些重要改进吧。[HN 讨论](https://news.ycombinator.com/item?id=36995735) -## Peer Type Resolution Improvements +## [Peer Type Resolution Improvements]($section.id('Peer Type Resolution Improvements')) 对等类型解析算法得到改进,下面是一些在 0.10 中不能解析,但在 0.11 中可以解析的例子: @@ -29,7 +29,7 @@ 而且现在使用 `@intCast` 这类 builtin 都只接受一个参数,目前类型根据上下文自动推断出来。 -## Multi-Object For Loops +## [Multi-Object For Loops]($section.id('Multi-Object For Loops')) 可以同时对多个对象进行遍历: @@ -45,7 +45,7 @@ for (input, 0..) |x, i| { } ``` -## @min and @max +## [@min and @max]($section.id('@min and @max')) 主要有两个改动: @@ -65,7 +65,7 @@ test "@min/@max refines result type" { } ``` -## @inComptime +## [@inComptime]($section.id('@inComptime')) 新加的 builtin,用于判断执行是否在 comptime 环境下执行: @@ -95,12 +95,12 @@ test "@inComptime" { } ``` -## 类型转化相关 builtin 的重命名 +## [类型转化相关 builtin 的重命名]($section.id('类型转化相关 builtin 的重命名')) 之前 `@xToY` 形式的 builtin 现在已经改成了 `@yFromX` ,这样的主要好处是便于阅读(从右向左),这是[草案](https://github.com/ziglang/zig/issues/6128)。 -## Tuple 类型声明 +## [Tuple 类型声明]($section.id('Tuple 类型声明')) 现在可以直接用无 field 名字的 struct 来声明 tuple 类型: @@ -133,7 +133,7 @@ test "tuple declarations" { + const testcases = [_]struct { []const u8, []const u8, bool }{ ``` -## 排序 +## [排序]($section.id('排序')) 现在排序算法分布两类: @@ -143,7 +143,7 @@ test "tuple declarations" { 与堆排的快速最差情况相结合,同时在具有特定模式的输入上达到线性时间。 -## Stack Unwinding +## [Stack Unwinding]($section.id('Stack Unwinding')) Zig 之前依赖 [frame pointer](https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers) @@ -154,20 +154,20 @@ pointer](https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers) DWARF unwind tables 和 MachO compact unwind information 来会恢复堆栈,详见:[\#15823](https://github.com/ziglang/zig/pull/15823)。 -## 包管理 +## [包管理]($section.id('包管理')) 0.11 首次正式引入了包管理器,具体解释可以参考:[Zig Build System](https://en.liujiacai.net/2023/04/13/zig-build-system/),而且很重要一点,step 之间可以[并发执行](https://ziglang.org/download/0.11.0/release-notes.html#Steps-Run-In-Parallel), -## Bootstrapping +## [Bootstrapping]($section.id('Bootstrapping')) C++ 实现的 Zig 编译器已经被彻底移除,这意味着 `-fstage1` 不再生效,Zig 现在只需要一个 2.4M 的 WebAssembly 文件和一个 C 编辑器即可,工作细节可以参考:[Goodbye to the C++ Implementation of Zig](https://ziglang.org/news/goodbye-cpp/)。 -## 代码生成 +## [代码生成]($section.id('代码生成')) 虽然 Zig 编译器现在还是主要使用 LLVM 来进行代码生成,但在这次发布中,其他几个后端也有了非常大的进步: @@ -182,7 +182,7 @@ Zig](https://ziglang.org/news/goodbye-cpp/)。 后端专注于为 OpenCL 内核生成代码,不过未来可能也会支持兼容 Vulkan 的着色器。 -## 增量编译 +## [增量编译]($section.id('增量编译')) 虽然这仍是一个高度 WIP 的功能,但这一版本周期中的许多改进为编译器的增量编译功能铺平了道路。其中最重要的是 @@ -190,7 +190,7 @@ Zig](https://ziglang.org/news/goodbye-cpp/)。 用户大多看不到这一改动,但它为编译器带来了许多好处,其中之一就是我们现在更接近增量编译了。增量编译将是 0.12.0 发布周期的重点。 -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) [Error Handling In Zig](https://www.aolium.com/karlseguin/4013ac14-2457-479b-e59b-e603c04673c8) 又一篇讨论错误处理的文章 @@ -222,7 +222,7 @@ Andrew 的最新文章,远离社交平台! [Taking off with Zig: Putting the Z in Benchmark — Double Trouble](https://double-trouble.dev/post/zbench/) -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) - [Mach v0.2 released](https://devlog.hexops.com/2023/mach-v0.2-released/) diff --git a/content/monthly/202309.smd b/content/monthly/202309.smd index ea627d7..5fea949 100644 --- a/content/monthly/202309.smd +++ b/content/monthly/202309.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 重大事件 +## [重大事件]($section.id('重大事件')) ## [Bounties Damage Open Source Projects](https://ziglang.org/news/bounties-damage-open-source-projects/) @@ -38,7 +38,7 @@ Bun 并不简单的是个 Node.js 替代品,而是大而全的工具链: - Testing libraries,内置 test runner,支持快照测试、模拟和代码覆盖,可以替代:jest、ts-test -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) [Kiesel Devlog \#1: Now passing 25% of test262](https://linus.dev/posts/kiesel-devlog-1/) 另一个 devlog,作者写了一个 JS engine 用来学习 Zig,该作者是 SerenityOS @@ -85,7 +85,7 @@ Mitchell Hashimoto 在这篇文章里分享了开发终端 Ghostty 时用的 Zig [SoA 内存布局]($image.siteAsset('images/soa-layout.webp')) -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) [ZigBrains](https://plugins.jetbrains.com/plugin/22456-zigbrains) A multifunctional Zig Programming Language plugin for the IDEA platform. diff --git a/content/monthly/202310.smd b/content/monthly/202310.smd index ef821dc..036b50c 100644 --- a/content/monthly/202310.smd +++ b/content/monthly/202310.smd @@ -6,9 +6,9 @@ .draft = false, --- -## 重大事件 +## [重大事件]($section.id('重大事件')) -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) [Notes From the Field: Learning Zig](https://registerspill.thorstenball.com/p/notes-from-the-field-learning-zig) Zig 初学者的使用经验分享 @@ -96,7 +96,7 @@ pub fn main() !void { 由于 Zig 还在快速开发迭代中,因此项目很有可能出现新版本 Zig 无法编译的情况,这篇文章介绍了一些管理多个 Zig 版本的方式。 -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) [zigcli](https://zigcli.liujiacai.net/) a toolkit for building command lines programs in Zig diff --git a/content/monthly/202311.smd b/content/monthly/202311.smd index 3845eea..94ad810 100644 --- a/content/monthly/202311.smd +++ b/content/monthly/202311.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 重大事件 +## [重大事件]($section.id('重大事件')) 本月讨论比较多的就是 [Zig May Pass Anything By Reference](https://www.1a-insec.net/blog/25-zig-reference-semantics/) @@ -58,7 +58,7 @@ precisely』,目前来看确实是这样的,而且 core team - [Design flaw: Swapping struct fields yields unexpected value \#12064](https://github.com/ziglang/zig/issues/12064) -## 观点/教程 +## [观点/教程]($section.id('观点/教程')) [Zig's std.json.Parsed(T)](https://www.openmymind.net/Zigs-std-json-Parsed/) 老朋友 openmymind 的文章,这篇文章主要讲述了使用 Zig 中的 json @@ -147,7 +147,7 @@ Fields - 一个包的兼容性,应该由文档来解释 - 增加私有字段,会增加语言的复杂度,而且这种复杂性本身是完全可以避免的 -## 项目/工具 +## [项目/工具]($section.id('项目/工具')) [zig build explained – building C/C++ projects](https://zig.news/xq/zig-build-explained-part-2-1850) 经典文章回顾,如何使用 Zig 构建系统编译 C/C++ 项目 diff --git a/content/monthly/202402.smd b/content/monthly/202402.smd index c5934dc..88e5aeb 100644 --- a/content/monthly/202402.smd +++ b/content/monthly/202402.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 重大事件 ($section.id('major-events')) +## [重大事件 ($section.id('major-events'))]($section.id('重大事件 ($section.id('major-events'))')) Andrew 最近在 zigshow 节目中介绍了 Zig 2024 年的规划,主要有以下几点: @@ -32,7 +32,7 @@ Andrew 最近在 zigshow 节目中介绍了 Zig 2024 年的规划,主要有以 \#91](https://github.com/orgs/zigcc/discussions/91) B 站搬运地址:https://www.bilibili.com/video/BV1UC4y167w9/ -## 观点/教程 ($section.id('opinion-tutorial')) +## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) [Fast-growing Zig tops Stack Overflow survey for highest-paid programming language](https://www.infoworld.com/article/3713082/fast-growing-zig-tops-stack-overflow-survey-for-highest-paid-programming-language.html) 估计是 Bun 带动的贫富差距?! @@ -67,7 +67,7 @@ Syndica 公司的 Sig,这是一款用 Zig 编写的、专注于 RPS 的 Solana - [Senior Software Engineer (Zig/C/Rust) @ Syndica](https://jobs.ashbyhq.com/syndica/15ab4e32-0f32-41a0-b8b0-16b6518158e9) -## 项目/工具 ($section.id('projects-tools')) +## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) [semickolon/kirei](https://github.com/semickolon/kirei) 🌸 The prettiest keyboard software diff --git a/content/monthly/202403.smd b/content/monthly/202403.smd index da86842..531d570 100644 --- a/content/monthly/202403.smd +++ b/content/monthly/202403.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 重大事件 ($section.id('major-events')) +## [重大事件 ($section.id('major-events'))]($section.id('重大事件 ($section.id('major-events'))')) > @@ -39,7 +39,7 @@ Kelley](https://rustacean-station.org/episode/andrew-kelley/) 也会紧密关注发布动态,有消息第一时间分享给大家。耐不住寂寞的朋友,可以先去刷刷 Zig 的 discord。 -## 观点/教程 ($section.id('opinion-tutorial')) +## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) [Redesign How Autodoc Works](https://github.com/ziglang/zig/pull/19208) Andrew 在这个 PR 里重构了现有的文档系统 @@ -235,7 +235,7 @@ const port = port: { [Using Zig with WebAssembly](https://blog.mjgrzymek.com/blog/zigwasm) 如何将 Zig 编译成 wasm,并传递复杂的参数。 -## 项目/工具 ($section.id('projects-tools')) +## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) [xataio/pgzx](https://github.com/xataio/pgzx) Create PostgreSQL extensions using Zig. 一个例子: diff --git a/content/monthly/202404.smd b/content/monthly/202404.smd index b7d6216..3b88fdf 100644 --- a/content/monthly/202404.smd +++ b/content/monthly/202404.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 重大事件 ($section.id('major-events')) +## [重大事件 ($section.id('major-events'))]($section.id('重大事件 ($section.id('major-events'))')) 千呼万唤的 0.12.0 版本终于 2024-04-20 正式释出了!这次版本历时 8 个月,有 268 位贡献者,一共进行了 3688 次提交!社区内的一些讨论:[Hacker @@ -37,7 +37,7 @@ CMake、Makefile、Vcpkg、Git submodule 等工具,所有的依赖使用 zon 期待一年后 Zig 的生态! -## 观点/教程 ($section.id('opinion-tutorial')) +## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) [Zig 中任意精度整数用途与实现](https://github.com/zigcc/forum/issues/112) 由于 CPU @@ -104,7 +104,7 @@ Vector,到最后利用 bit 的特点,来逐步优化,并用 godbolt [Documentation takes another step backwards : r/Zig](https://www.reddit.com/r/Zig/comments/1cc1x2v/documentation_takes_another_step_backwards/) 一个 Reddit 用户对文档的抱怨 -## 项目/工具 ($section.id('projects-tools')) +## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) [rofrol/zig-companies](https://github.com/rofrol/zig-companies) A list of companies using Zig in production. diff --git a/content/monthly/202405.smd b/content/monthly/202405.smd index 7ea076b..d62d74b 100644 --- a/content/monthly/202405.smd +++ b/content/monthly/202405.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 观点/教程 ($section.id('opinion-tutorial')) +## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) ## [Thoughts on Zig](https://arne.me/blog/thoughts-on-zig) ($section.id('thoughts-on-zig')) @@ -81,7 +81,7 @@ Openmymind } ``` -## 项目/工具 ($section.id('projects-tools')) +## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) [zigar](https://github.com/chung-leong/zigar) Enable the use of Zig code in JavaScript project。它可以让你直接在 JS diff --git a/content/monthly/202406.smd b/content/monthly/202406.smd index b9671fb..d2966f2 100644 --- a/content/monthly/202406.smd +++ b/content/monthly/202406.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 重大事件 ($section.id('major-events')) +## [重大事件 ($section.id('major-events'))]($section.id('重大事件 ($section.id('major-events'))')) 2024-06-07,0.13.0 发布,历时不足 2 个月,有 73 位贡献者,一共进行了 415 次提交! 这是一个相对较短的发布周期,主要原因是工具链升级,例如升级到 @@ -29,7 +29,7 @@ const map = std.StaticStringMap(T).initComptime(kvs_list); - 启用增量编译以实现快速重建。 - 将并发引入语义分析,进一步提高编译速度。 -## 观点/教程 ($section.id('opinion-tutorial')) +## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) ## [Leveraging Zig's Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/) ($section.id('zig-allocators')) @@ -156,7 +156,7 @@ Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较 其他社区的一些讨论:[Lobsters](https://lobste.rs/s/0mnhdx)、[Hacker News](https://news.ycombinator.com/item?id=40681862) -## 项目/工具 ($section.id('projects-tools')) +## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) [malcolmstill/zware](https://github.com/malcolmstill/zware) Zig WebAssembly Runtime Engine diff --git a/content/monthly/202407.smd b/content/monthly/202407.smd index 4248415..b8a6bcb 100644 --- a/content/monthly/202407.smd +++ b/content/monthly/202407.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 重大事件 ($section.id('major-events')) +## [重大事件 ($section.id('major-events'))]($section.id('重大事件 ($section.id('major-events'))')) 在[这篇文章](https://leaddev.com/tech/why-zig-one-hottest-programming-languages-learn)里,作者引用 Stackoverflow 2024 @@ -34,7 +34,7 @@ Zig 开发者的一些观点: 但与此同时,这些语言的工具链却非常糟糕。 Zig 允许用户涉猎这些核心编程语言,但可以使用更好的工具链,兼容各种语言和更丰富的功能。 -## 观点/教程 ($section.id('opinion-tutorial')) +## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) ## [Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/) ($section.id('improving-zls-experience')) @@ -167,7 +167,7 @@ fn get_window_messages() [65536][:0]const u8 { - 在 JavaScript 允许您内联表达式的某些情况下,您可能需要分解出一个变量来澄清生存期。看看[这个问题](https://github.com/ziglang/zig/issues/12414)。 -## 项目/工具 ($section.id('projects-tools')) +## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) [18alantom/fex](https://github.com/18alantom/fex) A command-line file explorer prioritizing quick navigation. diff --git a/content/monthly/202410.smd b/content/monthly/202410.smd index e072a1f..be7a141 100644 --- a/content/monthly/202410.smd +++ b/content/monthly/202410.smd @@ -6,9 +6,9 @@ .draft = false, --- -## 重大事件 ($section.id('major-events')) +## [重大事件 ($section.id('major-events'))]($section.id('重大事件 ($section.id('major-events'))')) -## 向 Zig 软件基金会认捐 30 万美元 ($section.id('donation-zsf-300k')) +## [向 Zig 软件基金会认捐 30 万美元 ($section.id('donation-zsf-300k'))]($section.id('向 Zig 软件基金会认捐 30 万美元 ($section.id('donation-zsf-300k'))')) Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金会 (ZSF) 捐赠了 300,000 美元。 @@ -25,7 +25,7 @@ Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金 作为其中的一部分,我们希望支持那些我们认为可以带来变革和影响的独立软件项目,这既是回馈给我如此之多的社区的一种方式,更重要的是,这也是彰显和鼓励为热爱而构建的文化的一种方式。 Zig 就是这样一个项目。 -## 观点/教程 ($section.id('opinion-tutorial')) +## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) ## [Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/) ($section.id('zig-vs-c')) @@ -84,7 +84,7 @@ Zig 官网已经用 [Zine](https://zine-ssg.io/) 重写! ## [Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig](https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html) ($section.id('zig-programmers-cashing-in')) -## 视频 ($section.id('videos')) +## [视频 ($section.id('videos'))]($section.id('视频 ($section.id('videos'))')) ### [I made an operating system that self replicates doom on a network "from scratch"](https://www.youtube.com/watch?v=xOySJpQlmv4&feature=youtu.be) ($section.id('os-self-replicates-doom')) @@ -92,7 +92,7 @@ Zig 官网已经用 [Zine](https://zine-ssg.io/) 重写! ### [Let's explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764) ($section.id('vulkan-api-zig')) -## 项目/工具 ($section.id('projects-tools')) +## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) [laohanlinux/boltdb-zig](https://github.com/laohanlinux/boltdb-zig) a zig implement kv database diff --git a/content/monthly/202411.smd b/content/monthly/202411.smd index da8ab44..eb8e410 100644 --- a/content/monthly/202411.smd +++ b/content/monthly/202411.smd @@ -6,7 +6,7 @@ .draft = false, --- -## 观点/教程 ($section.id('opinion-tutorial')) +## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) ## [Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html) ($section.id('js-toolchain-in-zig')) @@ -130,7 +130,7 @@ Zig,利用 LLVM 后端,以 wasm32-wasi 为目标生成的二进制文件。 - [Why is the bootstrapping process so complicated? : r/Zig](https://www.reddit.com/r/Zig/comments/142gwls/why_is_the_bootstrapping_process_so_complicated/) -## 项目/工具 ($section.id('projects-tools')) +## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) [Builds (Zig) - GoReleaser](https://goreleaser.com/customization/zig-builds/) 版本发布工具 GoReleaser 支持了 Zig diff --git a/content/post/2023-09-05-hello-world.smd b/content/post/2023-09-05-hello-world.smd index 42de01e..5ecb6fa 100644 --- a/content/post/2023-09-05-hello-world.smd +++ b/content/post/2023-09-05-hello-world.smd @@ -11,7 +11,7 @@ - [ZigCC 网站](https://ziglang.cc) - [ZigCC 公众号](https://github.com/zigcc/.github/raw/main/zig_mp.png) -## 供稿方式 +## [供稿方式]($section.id('供稿方式')) TODO @@ -27,6 +27,6 @@ date: '2023-09-05T16:13:13+0800' --- ``` -## 本地预览 +## [本地预览]($section.id('本地预览')) 在写完文章后,可以使用 [Hugo](https://gohugo.io/) 进行本地预览,只需在项目根目录执行 `hugo server`,这会启动一个 HTTP 服务,默认的访问地址是: http://localhost:1313/ diff --git a/content/post/2023-09-21-zig-midi.smd b/content/post/2023-09-21-zig-midi.smd index 47d03e4..8053b2f 100644 --- a/content/post/2023-09-21-zig-midi.smd +++ b/content/post/2023-09-21-zig-midi.smd @@ -56,7 +56,7 @@ MIDI 是"乐器数字接口"的缩写,是一种用于音乐设备之间通信 元事件主要存在于 MIDI 文件中,特别是在标准 MIDI 文件 (SMF) 的上下文中。在实时 MIDI 通信中,元事件通常不会被发送,因为它们通常不会影响音乐的实际播放。 -## Midi.zig +## [Midi.zig]($section.id('Midi.zig')) 本文件主要是处理 MIDI 消息的模块,为处理 MIDI 消息提供了基础结构和函数。 @@ -194,7 +194,7 @@ pub const Message = struct { - value 和 setValue 函数:用于获取和设置 MIDI 消息的值字段。 - Kind 枚举:定义了 MIDI 消息的所有可能种类,包括通道事件和系统事件。 -### midi 消息结构 +### [midi 消息结构]($section.id('midi 消息结构')) 我们需要先了解 MIDI 消息的一些背景。 @@ -211,7 +211,7 @@ pub const Message = struct { 4. **设置值**:`message.values = .{ ... }` 将这两个 7 位的值设置到 `message.values` 中。 -### 事件 +### [事件]($section.id('事件')) 针对事件,我们看 enum。 @@ -251,7 +251,7 @@ pub const Message = struct { 以下是对每个枚举成员的简要说明: -#### 频道事件 (Channel events) +#### [频道事件 (Channel events)]($section.id('频道事件 (Channel events)')) 1. **NoteOff**:这是一个音符结束事件,表示某个音符不再播放。 2. **NoteOn**:这是一个音符开始事件,表示开始播放某个音符。 @@ -261,7 +261,7 @@ pub const Message = struct { 6. **ChannelPressure**:频道压力事件,与多声道键盘压力相似,但它适用于整个频道,而不是特定音符。 7. **PitchBendChange**:音高弯曲变更事件,表示音符音高的上升或下降。 -#### 系统事件 (System events) +#### [系统事件 (System events)]($section.id('系统事件 (System events)')) 1. **ExclusiveStart**:独占开始事件,标志着一个独占消息序列的开始。 2. **MidiTimeCodeQuarterFrame**:MIDI 时间码四分之一帧,用于同步与其他设备。 @@ -276,11 +276,11 @@ pub const Message = struct { 11. **ActiveSensing**:活动感知事件,是一种心跳信号,表示设备仍然在线并工作。 12. **Reset**:重置事件,用于将设备重置为其初始状态。 -#### 其他 +#### [其他]($section.id('其他')) 1. **Undefined**:未定义事件,可能表示一个未在此枚举中定义的或无效的 MIDI 事件。 -## decode.zig +## [decode.zig]($section.id('decode.zig')) 本文件是对 MIDI 文件的解码器, 提供了一组工具,可以从不同的输入源解析 MIDI 文件的各个部分。这样可以方便地读取和处理 MIDI 文件。 @@ -498,7 +498,7 @@ pub fn file(reader: anytype, allocator: *mem.Allocator) !midi.File { 8. trackEvent: 从 reader 中解析一个 MIDI 轨道事件。它可以是 MIDI 消息或元事件。 9. file: 用于从 reader 解码一个完整的 MIDI 文件。它首先解码文件头,然后解码所有的文件块。这个函数会返回一个表示 MIDI 文件的结构体。 -### message 解析 +### [message 解析]($section.id('message 解析')) ```zig const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { @@ -531,7 +531,7 @@ const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { 4. `else return error.InvalidMessage;`: 如果 `first_byte` 不是状态字节,并且不存在前一个消息,那么返回一个 `InvalidMessage` 错误。 -## encode.zig +## [encode.zig]($section.id('encode.zig')) 本文件用于将 MIDI 数据结构编码为其对应的二进制形式。具体来说,它是将内存中的 MIDI 数据结构转换为 MIDI 文件格式的二进制数据。 @@ -672,7 +672,7 @@ pub fn file(writer: anytype, f: midi.File) !void { - file 函数:这是主函数,用于将整个 MIDI 文件数据结构编码为其二进制形式。它首先编码文件头,然后循环编码每个块和块中的事件。 -### int 函数 +### [int 函数]($section.id('int 函数')) ```zig @@ -731,7 +731,7 @@ pub fn int(writer: anytype, i: u28) !void { - 使用提供的`writer`将翻转后的字节写入到目标位置。 -## file.zig +## [file.zig]($section.id('file.zig')) 主要目的是为了表示和处理 MIDI 文件的不同部分,以及提供了一个迭代器来遍历 MIDI 轨道的事件。 @@ -912,7 +912,7 @@ pub const TrackIterator = struct { - 定义了一个 Result 结构来返回事件和关联的数据。 - 提供了一个`next`方法来读取下一个事件。 -## Build.zig +## [Build.zig]($section.id('Build.zig')) buid.zig 是一个 Zig 构建脚本(build.zig),用于配置和驱动 Zig 的构建过程。 diff --git a/content/post/2023-12-24-zig-build-explained-part1.smd b/content/post/2023-12-24-zig-build-explained-part1.smd index a47c646..b3e846a 100644 --- a/content/post/2023-12-24-zig-build-explained-part1.smd +++ b/content/post/2023-12-24-zig-build-explained-part1.smd @@ -15,11 +15,11 @@ Zig 构建系统仍然缺少文档,对很多人来说,这是不使用它的 我们将从一个刚刚初始化的 Zig 项目开始,逐步深入到更复杂的项目。在此过程中,我们将学习如何使用库和软件包、添加 C 代码,甚至如何创建自己的构建步骤。 -## 免责声明 +## [免责声明]($section.id('免责声明')) 由于我不会解释 Zig 语言的语法或语义,因此我希望你至少已经有了一些使用 Zig 的基本经验。我还将链接到标准库源代码中的几个要点,以便您了解所有这些内容的来源。我建议你阅读编译系统的源代码,因为如果你开始挖掘编译脚本中的函数,大部分内容都不言自明。所有功能都是在标准库中实现的,不存在隐藏的构建魔法。 -## 开始 +## [开始]($section.id('开始')) 我们通过新建一个文件夹来创建一个新项目,并在该文件夹中调用 zig init-exe。 @@ -56,7 +56,7 @@ pub fn build(b: *std.Build) void { } ``` -## 基础知识 +## [基础知识]($section.id('基础知识')) 构建系统的核心理念是,Zig 工具链将编译一个 Zig 程序 (build.zig),该程序将导出一个特殊的入口点(`pub fn build(b: *std.build.Builder) void`),当我们调用 `zig build` 时,该入口点将被调用。 @@ -95,7 +95,7 @@ Step 遵循与 std.mem.Allocator 相同的接口模式,需要实现一个 make 现在,我们需要创建一个稍正式的 Zig 程序: -## 编译 Zig 源代码 +## [编译 Zig 源代码]($section.id('编译 Zig 源代码')) 要使用编译系统编译可执行文件,编译器需要使用函数 Builder.addExecutable,它将为我们创建一个新的 LibExeObjStep。这个步骤实现是 zig build-exe、zig build-lib、zig build-obj 或 zig test 的便捷封装,具体取决于初始化方式。本文稍后将对此进行详细介绍。 @@ -118,7 +118,7 @@ pub fn build(b: *std.build.Builder) void { 这将始终在当前机器的调试模式下编译,因此对于初学者来说,这可能就足够了。但如果你想开始发布你的项目,你可能需要启用交叉编译: -## 交叉编译 +## [交叉编译]($section.id('交叉编译')) 交叉编译是通过设置程序的目标和编译模式来实现的 @@ -187,7 +187,7 @@ zig build -Doptimize=ReleaseSmall 但我们仍然必须调用 zig build 编译,因为默认调用仍然没有任何作用。让我们改变一下! -## 安装工件 +## [安装工件]($section.id('安装工件')) 要安装任何东西,我们必须让它依赖于构建器的安装步骤。该步骤是已创建的,可通过 Builder.getInstallStep() 访问。我们还需要创建一个新的 InstallArtifactStep,将我们的 exe 文件复制到安装目录(通常是 zig-out) @@ -254,7 +254,7 @@ pub fn build(b: *std.build.Builder) void { 现在,从理解初始构建脚本到完全扩展,还缺少一个部分: -## 运行已构建的应用程序 +## [运行已构建的应用程序]($section.id('运行已构建的应用程序')) 为了开发用户体验和一般便利性,从构建脚本中直接运行程序是非常实用的。这通常是通过运行步骤实现的,可以通过 zig build run 调用。 @@ -328,7 +328,7 @@ pub fn build(b: *std.build.Builder) void { zig build run -- -o foo.bin foo.asm ``` -## 结论 +## [结论]($section.id('结论')) 本系列的第一章应该能让你完全理解本文开头的构建脚本,并能创建自己的构建脚本。 diff --git a/content/post/2023-12-28-zig-build-explained-part2.smd b/content/post/2023-12-28-zig-build-explained-part2.smd index d68c0ef..3736639 100644 --- a/content/post/2023-12-28-zig-build-explained-part2.smd +++ b/content/post/2023-12-28-zig-build-explained-part2.smd @@ -9,15 +9,15 @@ > - 原文链接: https://zig.news/xq/zig-build-explained-part-2-1850 > - API 适配到 Zig 0.11.0 版本 -## 注释 +## [注释]($section.id('注释')) 从现在起,我将只提供一个最精简的 build.zig,用来说明解决一个问题所需的步骤。如果你想了解如何将所有这些文件粘合到一个构建文件中,请阅读本系列[第一篇文章](2023-12-24-zig-build-explained-part1)。 -## 在命令行上编译 C 代码 +## [在命令行上编译 C 代码]($section.id('在命令行上编译 C 代码')) Zig 有两种编译 C 代码的方法,而且这两种很容易混淆。 -### 使用 zig cc +### [使用 zig cc]($section.id('使用 zig cc')) Zig 提供了 LLVM c 编译器 clang。第一种是 zig cc 或 zig c++,它是与 clang 接近 1:1 的前端。由于我们无法直接从 build.zig 访问这些功能(而且我们也不需要!),所以我将在快速的介绍这个主题。 @@ -41,7 +41,7 @@ zig cc -o example.exe -target x86_64-windows-gnu buffer.c main.c 如你所见,只需向 -target 传递目标三元组,就能调用交叉编译。只需确保所有外部库都已准备好进行交叉编译即可! -## 使用 zig build-exe 和其他工具 +## [使用 zig build-exe 和其他工具]($section.id('使用 zig build-exe 和其他工具')) 使用 Zig 工具链构建 C 项目的另一种方法与构建 Zig 项目的方法相同: @@ -59,7 +59,7 @@ zig build-exe -lc -target x86_64-windows-gnu main.c buffer.c 你会发现,使用这条编译命令,Zig 会自动在输出文件中附加 .exe 扩展名,并生成 .pdb 调试数据库。如果你在此处传递 --name example,输出文件也会有正确的 .exe 扩展名,所以你不必考虑这个问题。 -## 用 build.zig 创建 C 代码 +## [用 build.zig 创建 C 代码]($section.id('用 build.zig 创建 C 代码')) 那么,我们如何用 build.zig 来构建上面的两个示例呢? @@ -112,7 +112,7 @@ exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("buffer.c"), .flags = exe.addCSourceFile(.{.file = std.build.LazyPath.relative("buffer.c"), .flags = &.{"-fno-sanitize=undefined"}}); ``` -## 使用外部库 +## [使用外部库]($section.id('使用外部库')) 通常情况下,C 项目依赖于其他库,这些库通常预装在 Unix 系统中,或通过软件包管理器提供。 @@ -181,7 +181,7 @@ zig build ./zig-out/bin/downloader https://mq32.de/public/ziggy.txt ``` -## 配置路径 +## [配置路径]($section.id('配置路径')) 由于我们不能在交叉编译项目中使用 pkg-config,或者我们想使用预编译的专用库(如 BASS 音频库),因此我们需要配置包含路径和库路径。 @@ -220,7 +220,7 @@ pub fn build(b: *std.Build) void { addIncludePath 和 addLibraryPath 都可以被多次调用,以向编译器添加多个路径。这些函数不仅会影响 C 代码,还会影响 Zig 代码,因此 @cImport 可以访问包含路径中的所有头文件。 -## 每个文件的包含路径 +## [每个文件的包含路径]($section.id('每个文件的包含路径')) 因此,如果我们需要为每个 C 文件设置不同的包含路径,我们就需要用不同的方法来解决这个问题: 由于我们仍然可以通过 addCSourceFile 传递任何 C 编译器标志,因此我们也可以在这里手动设置包含目录。 @@ -252,7 +252,7 @@ pub fn build(b: *std.Build) void { 上面的示例非常简单,所以你可能会想为什么需要这样的东西。答案是,有些库的头文件名称非常通用,如 api.h 或 buffer.h,而您希望使用两个共享头文件名称的不同库。 -## 构建 C++ 项目 +## [构建 C++ 项目]($section.id('构建 C++ 项目')) 到目前为止,我们只介绍了 C 文件,但构建 C++ 项目并不难。你仍然可以使用 addCSourceFile,但只需传递一个具有典型 C++ 文件扩展名的文件,如 cpp、cxx、c++ 或 cc: @@ -285,7 +285,7 @@ pub fn build(b: *std.Build) void { 这就是构建 C++ 文件所需的全部知识,没有什么更神奇的了。 -## 指定语言版本 +## [指定语言版本]($section.id('指定语言版本')) 试想一下,如果你创建了一个庞大的项目,其中的 C 或 C++ 文件有新有旧,而且可能是用不同的语言标准编写的。为此,我们可以使用编译器标志来传递 -std=c90 或 -std=c++98: @@ -320,7 +320,7 @@ pub fn build(b: *std.Build) void { } ``` -## 条件编译 +## [条件编译]($section.id('条件编译')) 与 Zig 相比,C 和 C++ 的条件编译方式非常繁琐。由于缺乏惰性求值的功能,有时必须根据目标环境来包含/排除文件。你还必须提供宏定义来启用/禁用某些项目功能。 @@ -373,7 +373,7 @@ pub fn build(b: *std.Build) void { 有条件地包含文件就像使用 if 一样简单,你可以这样做。只要不根据你想在构建脚本中定义的任何约束条件调用 addCSourceFile 即可。只包含特定平台的文件?看看上面的脚本就知道了。根据系统时间包含文件?也许这不是个好主意,但还是有可能的! -## 编译大型项目 +## [编译大型项目]($section.id('编译大型项目')) 由于大多数 C(更糟糕的是 C++)项目都有大量文件(SDL2 有 411 个 C 文件和 40 个 C++ 文件),我们必须找到一种更简单的方法来编译它们。调用 addCSourceFile 400 次并不能很好地扩展。 @@ -478,7 +478,7 @@ pub fn build(b: *std.build.Builder) !void { 注意:其他构建系统会考虑文件名,而 Zig 系统不会!例如,在一个 qmake 项目中不能有两个名为 data.c 的文件!Zig 并不在乎,你可以添加任意多的同名文件,只要确保它们在不同的文件夹中就可以了 😏。 -## 编译 Objective C +## [编译 Objective C]($section.id('编译 Objective C')) 我完全忘了!Zig 不仅支持编译 C 和 C++,还支持通过 clang 编译 Objective C! @@ -514,7 +514,7 @@ pub fn build(b: *std.Build) void { 在这里,链接 libc 是隐式的,因为添加框架会自动强制链接 libc。是不是很酷? -## 混合使用 C 和 Zig 源代码 +## [混合使用 C 和 Zig 源代码]($section.id('混合使用 C 和 Zig 源代码')) 现在,是最后一章: 混合 C 代码和 Zig 代码! @@ -554,7 +554,7 @@ pub fn build(b: *std.Build) void { 您应用程序的入口点现在必须在 Zig 代码中,因为根文件必须导出一个 pub fn main(...) ……。 因此,如果你想将 C 项目中的代码移植到 Zig 中,你必须将 argc 和 argv 转发到你的 C 代码中,并将 C 代码中的 main 重命名为其他函数(例如 oldMain),然后在 Zig 中调用它。如果需要 argc 和 argv,可以通过 std.process.argsAlloc 获取。或者更好: 在 Zig 中重写你的入口点,然后从你的项目中移除一些 C 语言! -## 结论 +## [结论]($section.id('结论')) 假设你只编译一个输出文件,那么现在你应该可以将几乎所有的 C/C++ 项目移植到 build.zig。 diff --git a/content/post/2023-12-29-zig-build-explained-part3.smd b/content/post/2023-12-29-zig-build-explained-part3.smd index b69f3e6..08dea9d 100644 --- a/content/post/2023-12-29-zig-build-explained-part3.smd +++ b/content/post/2023-12-29-zig-build-explained-part3.smd @@ -11,17 +11,17 @@ 从现在起,我将只提供一个最精简的 build.zig,用来说明解决一个问题所需的步骤。如果你想了解如何将所有这些文件粘合到一个构建文件中,请阅读本系列[第一篇文章](2023-12-24-zig-build-explained-part1)。 -## 复合项目 +## [复合项目]($section.id('复合项目')) 有很多简单的项目只包含一个可执行文件。但是,一旦开始编写库,就必须对其进行测试,通常会编写一个或多个示例应用程序。当人们开始使用外部软件包、C 语言库、生成代码等时,复杂性也会随之上升。 本文试图涵盖所有这些用例,并将解释如何使用 build.zig 来编写多个程序和库。 -## 软件包 +## [软件包]($section.id('软件包')) 译者:此处代码和说明,需要 zig build-exe --pkg-begin,但是在 0.11 已经失效。所以删除。 -## 库 +## [库]($section.id('库')) 但 Zig 也知道库这个词。但我们不是已经讨论过外部库了吗? @@ -36,7 +36,7 @@ 在 Zig 中,我们需要导入库的头文件,如果头文件在 Zig 中,则使用包,如果是 C 语言头文件,则使用 @cImport。 -## 工具 +## [工具]($section.id('工具')) 如果我们的项目越来越多,那么在构建过程中就需要使用工具。这些工具通常会完成以下任务: @@ -48,7 +48,7 @@ 但我们如何在 build.zig 中完成这些工作呢? -## 添加软件包 +## [添加软件包]($section.id('添加软件包')) 添加软件包通常使用 LibExeObjStep 上的 addPackage 函数。该函数使用一个 std.build.Pkg 结构来描述软件包的外观: @@ -98,7 +98,7 @@ exe.addModule("lola",pkgs.lola); exe.addModule("args",pkgs.args); ``` -## 添加库 +## [添加库]($section.id('添加库')) 添加库相对容易,但我们需要配置更多的路径。 @@ -106,7 +106,7 @@ exe.addModule("args",pkgs.args); 假设我们要将 libcurl 链接到我们的项目,因为我们要下载一些文件。 -### 系统库 +### [系统库]($section.id('系统库')) 对于 unixoid 系统,我们通常可以使用系统软件包管理器来链接系统库。方法是调用 linkSystemLibrary,它会使用 pkg-config 自行找出所有路径: @@ -137,7 +137,7 @@ pub fn build(b: *std.Build) void { 对于 Linux 系统,这是链接外部库的首选方式。 -### 本地库 +### [本地库]($section.id('本地库')) 不过,您也可以链接您作为二进制文件提供商的库。为此,我们需要调用几个函数。首先,让我们来看看这样一个库是什么样子的: @@ -167,7 +167,7 @@ include 我们可以看到,vendor/libcurl/include 路径包含我们的头文件,vendor/libcurl/lib 文件夹包含一个静态库(libcurl.a)和一个共享/动态库(libcurl.so)。 -### 动态链接 +### [动态链接]($section.id('动态链接')) 要链接 libcurl,我们需要先添加 include 路径,然后向 zig 提供库的前缀和库名:(todo 代码有待验证,因为 curl 可能需要自己编译自己生成 static lib) @@ -197,7 +197,7 @@ addLibraryPath 对库文件也有同样的作用。这意味着 Zig 现在也会 最后,linkSystemLibrary 会告诉 Zig 搜索名为 "curl "的库。如果你留心观察,就会发现上面列表中的文件名是 libcurl.so,而不是 curl.so。在 unixoid 系统中,库文件的前缀通常是 lib,这样就不会将其传递给系统。在 Windows 系统中,库文件的名字应该是 curl.lib 或类似的名字。 -## 静态链接 +## [静态链接]($section.id('静态链接')) 当我们要静态链接一个库时,我们必须采取一些不同的方法: @@ -250,7 +250,7 @@ pub fn build(b: *std.build.Builder) void { 我们可以继续静态链接越来越多的库,并拉入完整的依赖关系树。 -## 通过源代码链接库 +## [通过源代码链接库]($section.id('通过源代码链接库')) 不过,我们还有一种与 Zig 工具链截然不同的链接库方式: @@ -293,7 +293,7 @@ pub fn build(b: *std.build.Builder) void { 这一点尤其方便,因为我们可以使用 setTarget 和 setBuildMode 从任何地方编译到任何地方。 -## 使用工具 +## [使用工具]($section.id('使用工具')) 在工作流程中使用工具,通常是在需要以 bison、flex、protobuf 或其他形式进行预编译时。工具的其他用例包括将输出文件转换为不同格式(如固件映像)或捆绑最终应用程序。 @@ -352,7 +352,7 @@ size 是一个很好的工具,它可以输出有关可执行文件代码大小 如您所见,我们在这里使用了 addArtifactArg,因为 addSystemCommand 只会返回一个 std.build.RunStep。这样,我们就可以增量构建完整的命令行,包括任何 LibExeObjStep 输出、FileSource 或逐字参数。 -## 全新工具 +## [全新工具]($section.id('全新工具')) 最酷的是 我们还可以从 LibExeObjStep 获取 std.build.RunStep: @@ -389,7 +389,7 @@ pub fn build(b: *std.build.Builder) void { 调用 zig build pack 时,我们将运行 tools/pack.zig。这很酷,因为我们还可以从头开始编译所需的工具。为了获得最佳的开发体验,你甚至可以从源代码编译像 bison 这样的 "外部 "工具,这样就不会依赖系统了! -## 将所有内容放在一起 +## [将所有内容放在一起]($section.id('将所有内容放在一起')) 一开始,所有这些都会让人望而生畏,但如果我们看一个更大的 build.zig 实例,就会发现一个好的构建文件结构会给我们带来很大帮助。 @@ -491,7 +491,7 @@ pub fn build(b: *std.build.Builder) void { 两者都是为了在主机平台上运行,而不是在目标机器上。 此外,deploy_tool 还设置了固定的编译模式,因为我们希望快速编译,即使我们编译的是应用程序的调试版本。 -## 总结 +## [总结]($section.id('总结')) 看完这一大堆文字,你现在应该可以构建任何你想要的项目了。我们已经学会了如何编译 Zig 应用程序,如何为其添加任何类型的外部库,甚至如何为发布管理对应用程序进行后处理。 diff --git a/content/post/2024-01-12-how-to-release-your-zig-applications.smd b/content/post/2024-01-12-how-to-release-your-zig-applications.smd index d72f9a1..26764c6 100644 --- a/content/post/2024-01-12-how-to-release-your-zig-applications.smd +++ b/content/post/2024-01-12-how-to-release-your-zig-applications.smd @@ -14,7 +14,7 @@ You've just written an application in Zig and want others to use it. A convenient way for users to use your application is to provide a pre-built executable file. Next, I'll discuss the two main things that need to be handled correctly in a good release process. -## Why provide pre-built executable files? +## [Why provide pre-built executable files?]($section.id('Why provide pre-built executable files?')) Given how C/C++ dependencies work (or don't work), for some C/C++ projects, providing pre-compiled executable files is almost a necessity, @@ -26,7 +26,7 @@ That said, the more popular your application is, the less users will care what l Your users don't want to install Zig and run the build process to easily use your application (99% of the time, the rest 1% will be discussed later), so it's still better to pre-build your application. -## `zig build` vs `zig build-exe` +## [`zig build` vs `zig build-exe`]($section.id('`zig build` vs `zig build-exe`')) In this article, we'll see how to make, release a build for a Zig project, so it's worth spending a little time to fully understand the relationship between Zig build system and command line. @@ -48,7 +48,7 @@ what `zig build` does is to prepare command line parameters for `build-exe`. This means that in compiling Zig code, `zig build` (assuming the code in `build.zig` is correct) and `zig build-exe` are one-to-one correspondence. The only difference is convenience. -## Build mode +## [Build mode]($section.id('Build mode')) When building a Zig project with `zig build` or `zig build-exe myapp.zig`, the default is a debug build executable file. Debug build is mainly for development convenience, so it's usually considered unsuitable for release. @@ -70,7 +70,7 @@ but it's not prioritized for performance, but rather for trying to minimize the For example, this is a very meaningful build mode for WebAssembly, because you want the executable file to be as small as possible, and the sandbox runtime environment has provided a lot of security guarantees. -## How to set build mode +## [How to set build mode]($section.id('How to set build mode')) When using `zig build-exe`, you can add `-O ReleaseSafe` (or `ReleaseFast`, or `ReleaseSmall`) to get the corresponding build mode. @@ -98,7 +98,7 @@ const exe = b.addExecutable(.{ This is how you specify release mode in command line: `zig build -Doptimize=ReleaseSafe` (or `-Doptimize=ReleaseFast`, or `-Doptimize=ReleaseSmall`). -## Choose the correct build target +## [Choose the correct build target]($section.id('Choose the correct build target')) Now, we've chosen the correct release mode, it's time to consider build target. Obviously, if the platform used and build platform are different, the corresponding build target needs to be specified, @@ -109,7 +109,7 @@ The most straightforward way is to directly call `zig build` or `zig build-exe` If you do this, sometimes it works, but sometimes it crashes due to `illegal instruction` (or similar error). What's happening? -## CPU features +## [CPU features]($section.id('CPU features')) If build target is not specified when building, Zig will build for the current machine, which means it will use all instruction sets supported by the current CPU. If the CPU supports AVX extension, @@ -173,7 +173,7 @@ const exe = b.addExecutable(.{ This also means if you want to add other restrictions or change how to specify target when building, you can achieve this by adding your own code. -## Conclusion +## [Conclusion]($section.id('Conclusion')) Now you've learned the things you need to ensure correctly when releasing a build: choose a release optimization mode and choose the correct build target, including building for the same system you're building. diff --git a/content/post/2024-04-06-zig-cpp.smd b/content/post/2024-04-06-zig-cpp.smd index 4328542..0713036 100644 --- a/content/post/2024-04-06-zig-cpp.smd +++ b/content/post/2024-04-06-zig-cpp.smd @@ -8,7 +8,7 @@ 尽管 Zig 社区宣称 Zig 语言是一个更好的 C (better C),但是我个人在学习 Zig 语言时经常会"触类旁通"C++。在这里列举一些例子来说明我的一些体会,可能会有一些不正确的地方,欢迎批评指正。 -## "元能力" vs "元类型" +## ["元能力" vs "元类型"]($section.id('"元能力" vs "元类型"')) 在我看来,C++的增强方式是希望赋予语言一种"元能力",能够让人重新发明新的类型,使得使用 C++的程序员使用自定义的类型,进行一种类似于"领域内语言"(DSL)编程。一个通常的说法就是 C++中任何类型定义都像是在模仿基本类型`int`。比如我们有一种类型 T,那么我们则需要定义 T 在以下几种使用场景的行为: @@ -26,7 +26,7 @@ x = y; //x的类型是T,复制运算符重载,当然也有可能是移动运 不过话说回来,很多底层系统的开发需求往往和这种类型系统的构建相悖,比如如果你的类型就是一个`int`的封装,那么即使发生拷贝你也无所谓性能开销。但是如果是一个`struct`,那么通常情况下,你会比较 care 拷贝,而可能考虑"移动"之类的手段。这个时候各种 C++的提供的幻觉,就成了程序员开发的绊脚石,经常你需要分析一段 C++表达式里到底有没有发生拷贝,他是左值还是右值,其实你在写 C 语言的时候也很少去考虑了这些,你在 Zig 里同样也不需要。 -## 类型系统 +## [类型系统]($section.id('类型系统')) C 语言最大弊病就是没有提供标准库,C++的标准库你要是能看懂,得具备相当的 C++的语法知识,但是 Zig 的标准库几乎不需要文档就能看懂。这其实是因为,在 C++里,类型不是一等成员(first class member),因此实现一些模版元编程算法特别不直观。但是在 Zig 里,`type`就是一等成员,比如你可以写: @@ -63,7 +63,7 @@ Some::OutputType 相当于对于 InputType 调用一个 Some"函数",然后输出一个 OutputType。 -## 命令式 VS 声明式 +## [命令式 VS 声明式]($section.id('命令式 VS 声明式')) 比如实现一个函数,输入一个 bool 值,根据 bool 值,如果为真,那么输出 type A,如果为假那么输出 type B。 @@ -163,6 +163,6 @@ pub fn main() void { 即这个也是完全命令式的。当然 C++20 之后也出现了`if constexpr`和`concept`来进一步简化模版元编程,C++的元编程也在向命令式的方向进化。 -## 结束语 +## [结束语]($section.id('结束语')) 尽管 Zig 目前"还不成熟",但是学习 Zig,如果采用一种对照的思路,偶尔也会"触类旁通"C++,达到举一反三的效果。 diff --git a/content/post/2024-05-07-package-hash.smd b/content/post/2024-05-07-package-hash.smd index 7eea64c..ac1cd22 100644 --- a/content/post/2024-05-07-package-hash.smd +++ b/content/post/2024-05-07-package-hash.smd @@ -10,7 +10,7 @@ > 原文地址:[build.zig.zon dependency hashes](https://zig.news/michalsieron/buildzigzon-dependency-hashes-47kj) -## 引言 +## [引言]($section.id('引言')) 作者 Michał Sieroń 最近在思考 `build.zig.zon` 中的依赖项哈希值的问题。这些哈希值都有相同的前缀,而这对加密哈希函数来说极其不同寻常。习惯性使用 Conda 和 Yocto 对下载的压缩包运行 sha256sum,但生成的摘要与 `build.zig.zon` 中的哈希值完全不同。 @@ -34,7 +34,7 @@ 以上摘取自 [hexops/mach](https://github.com/hexops/mach/blob/bffc66800584123e2844c4bc4b577940806f9088/build.zig.zon#L13-L26) 项目。 -## 初步探索 +## [初步探索]($section.id('初步探索')) 经过一番探索,我找到了一个文档:[doc/build.zig.zon.md](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/doc/build.zig.zon.md),似乎没有任何线索指向它。而文档中对哈希有段简短的描述。 @@ -44,7 +44,7 @@ 该哈希值是基于一系列文件内容计算得出的,这些文件是在获取URL后并应用了路径规则后得到的。 这个字段是最重要的;一个包是的唯一性是由它的哈希值确定的,不同的 URL 可能对应同一个包。 -## 多重哈希 +## [多重哈希]($section.id('多重哈希')) 在他们的网站上有一个很好的可视化展示,说明了这一过程: [多重哈希](https://multiformats.io/multihash/)。 @@ -52,7 +52,7 @@ 因此 `build.zig.zon` 中的哈希字段不仅包含了摘要(digest),还包含了一些元数据(metadata)。但即使我们丢弃了头部信息,得到的结果仍与下载的 `tar` 包的 `sha256` 摘要不相符。而这就涉及到了包含规则的问题。 -## 包含规则(inclusion rules) +## [包含规则(inclusion rules)]($section.id('包含规则(inclusion rules)')) 回到 [doc/build.zig.zon.md](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/doc/build.zig.zon.md) 文件,我们看到: @@ -93,7 +93,7 @@ pub fn includePath(self: Filter, sub_path: []const u8) bool { 除此之外,这个函数会检查 `sub_path` 是否被明确列出,或者是已明确列出的目录的子目录。 -## 计算哈希 +## [计算哈希]($section.id('计算哈希')) 现在我们知道了 `build.zig.zon` 的包含规则,也知道使用了 `SHA256` 算法。但我们仍然不知道实际的哈希结果是如何得到的。例如,它可能是通过将所有包含的文件内容输入哈希器来计算的。所以让我们再仔细看看,也许我们可以找到答案。 @@ -121,7 +121,7 @@ try all_files.append(hashed_file); 跟踪 `workerHashFile`,我们看到它是 `hashFileFallible` 的一个简单包装,而后者看起来相当复杂。让我们来分解一下。 -## 单个文件的哈希计算 +## [单个文件的哈希计算]($section.id('单个文件的哈希计算')) 首先,会进行一些初始化设置,其中创建并用规整后的文件路径初始化了一个新的哈希器实例: @@ -169,7 +169,7 @@ hasher.update(link_name); 首先进行路径分隔符的规整,保证不同平台一致,之后将符号链接的目标路径输入 `hasher`。在 `hashFileFallible` 函数最后,把计算出的哈希值赋值给 `HashedFile` 对象的 `hash` 字段。 -## 组合哈希 +## [组合哈希]($section.id('组合哈希')) 尽管有了单个文件的哈希值,但我们仍不知道如何得到最终的哈希。幸运的是,曙光就在眼前。 @@ -199,13 +199,13 @@ for (all_files.items) |hashed_file| { 在这里我们看到所有计算出的哈希被一个接一个地输入到一个新的哈希器中。在 `computeHash` 的最后,我们返回 `hasher.finalResult()`,现在我们明白哈希值是如何获得的了。 -## 最终多哈希值 +## [最终多哈希值]($section.id('最终多哈希值')) 现在我们有了一个 `SHA256` 摘要,可以最终返回到 `main.zig`,在那里我们调用 [`Package.Manifest.hexDigest(fetch.actual_hash)`](https://github.com/ziglang/zig/blob/9d64332a5959b4955fe1a1eac793b48932b4a8a8/src/Package/Manifest.zig#L174)。在那里,我们将多哈希头写入缓冲区,之后是我们的组合摘要。 顺便说一下,我们看到所有哈希头都是 `1220` 并非巧合。这是因为 `Zig` [硬编码了 SHA256](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Manifest.zig#L3) - 0x12,它有 32 字节的摘要 - 0x20。 -## 总结 +## [总结]($section.id('总结')) 总结一下:最终哈希值是一个多哈希头 + `SHA256` 摘要。 @@ -215,7 +215,7 @@ for (all_files.items) |hashed_file| { 在实验这个之后,我有一个想法,我很惊讶 Zig 没有检查 `build.zig.zon` 中列出的所有文件是否存在。但这可能是另一天的话题了。 -## 译者注 +## [译者注]($section.id('译者注')) 在使用本地包时,可以使用下面的命令进行 hash 问题的排查: diff --git a/content/post/2024-05-24-interface-idioms.smd b/content/post/2024-05-24-interface-idioms.smd index 57c676c..f742595 100644 --- a/content/post/2024-05-24-interface-idioms.smd +++ b/content/post/2024-05-24-interface-idioms.smd @@ -8,7 +8,7 @@ > 原文链接: https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj -## 引言 +## [引言]($section.id('引言')) 在 Java 和 Go 中,可以使用“接口”(一组方法或方法集)定义基于行为的抽象。通常接口包含所谓的虚表(`vtable`) 以实现动态分派。Zig 允许在结构体、枚举、联合和不透明类型中声明函数和方法,尽管 Zig 尚未支持接口作为一种语言特性。 @@ -31,7 +31,7 @@ Zig 标准库应用了一些代码习语或模式以达到类似效果。 完整代码位于[此仓库](https://github.com/yglcode/zig_interfaces),你可以使用 `zig test interfaces.zig` 运行它。 -## 背景设定 +## [背景设定]($section.id('背景设定')) 让我们使用经典的面向对象编程示例,创建一些形状:点(`Point`)、盒子(`Box`)和圆(`Circle`)。 @@ -86,7 +86,7 @@ fn init_data() struct { point: Point, box: Box, circle: Circle } { } ``` -## 接口1:枚举标签联合 +## [接口1:枚举标签联合]($section.id('接口1:枚举标签联合')) Loris Cro 在[“使用 Zig 0.10.0 轻松实现接口”](https://zig.news/kristoff/easy-interfaces-with-zig-0100-2hc5) 中介绍了使用枚举标签联合作为接口的方法。这是最简单的解决方案,尽管你必须在联合类型中显式列出所有“实现”该接口的变体类型。 @@ -126,7 +126,7 @@ test "union_as_intf" { } ``` -## 接口2:vtable 和动态分派的第一种实现 +## [接口2:vtable 和动态分派的第一种实现]($section.id('接口2:vtable 和动态分派的第一种实现')) Zig 已从最初基于嵌入式 `vtab` 和 `#fieldParentPtr()` 的动态分派切换到基于“胖指针”接口的以下模式; 请查阅此文章了解更多细节[“Allocgate 将在 Zig 0.9 中到来...”](https://pithlessly.github.io/allocgate.html)。 @@ -198,7 +198,7 @@ test "vtab1_as_intf" { } ``` -## 接口3:vtable 和动态分派的第二种实现 +## [接口3:vtable 和动态分派的第二种实现]($section.id('接口3:vtable 和动态分派的第二种实现')) 在上述第一种实现中,通过 `Shape2.init()` 将 `Box` “转换”为接口 `Shape2` 时,会对 `box` 实例进行类型检查, 以确保其实现了 `Shape2` 的方法(包括名称的匹配签名)。第二种实现中有两个变化: @@ -271,7 +271,7 @@ test "vtab2_as_intf" { } ``` -## 接口4:使用嵌入式 vtab 和 @fieldParentPtr() 的原始动态分派 +## [接口4:使用嵌入式 vtab 和 @fieldParentPtr() 的原始动态分派]($section.id('接口4:使用嵌入式 vtab 和 @fieldParentPtr() 的原始动态分派')) 接口 `std.build.Step` 和所有构建步骤 `std.build.[RunStep, FmtStep, ...]` 仍然使用这种模式。 @@ -364,7 +364,7 @@ test "vtab3_embedded_in_struct" { } ``` -## 接口5:编译时的泛型接口 +## [接口5:编译时的泛型接口]($section.id('接口5:编译时的泛型接口')) 所有上述接口都侧重于 `vtab` 和动态分派:接口值将隐藏其持有的具体值的类型。因此,你可以将这些接口值放入数组中并统一处理。 diff --git a/content/post/2024-06-10-zig-hashmap-1.smd b/content/post/2024-06-10-zig-hashmap-1.smd index 79c68fe..26d242b 100644 --- a/content/post/2024-06-10-zig-hashmap-1.smd +++ b/content/post/2024-06-10-zig-hashmap-1.smd @@ -24,7 +24,7 @@ pub fn put(self: *Self, key: K, value: V) Allocator.Error!void { 正如我所说,大部分繁重的工作都由 `std.HashMapUnmanaged` 完成,其他变体通过一个名为 `unmanaged` 的字段对其进行封装。 -## Unmanaged +## [Unmanaged]($section.id('Unmanaged')) 在Zig标准库中随处可见的类型命名约定是 `unmanaged`。这种命名方式表明所涉及的类型不维护 `allocator`。任何需要分配内存的方法都会显式地将 `allocator` 作为参数传递。要实际看到这一点,可以考虑下面这个链表的例子: @@ -143,7 +143,7 @@ pub fn LinkedList(comptime T: type) type { 为了简化,本文的其余部分不会再提到 `unmanaged`。我们看到关于 `StringHashMap` 或 `AutoHashMap` 或 `HashMap` 的任何内容同样适用于它们的 Unmanaged 变体。 -## HashMap 与 AutoHashMap +## [HashMap 与 AutoHashMap]($section.id('HashMap 与 AutoHashMap')) std.HashMap 是一个泛型类型,它接受两个类型参数:键的类型和值的类型。正如我们所见,哈希映射需要两个函数:hash 和 eql。这两个函数合起来被称为“上下文(context)”。这两个函数都作用于键,并且没有一个单一的 hash 或 eql 函数适用于所有类型。例如,对于整数键,eql 将是 `a_key == b_key`;而对于 `[]const u8` 键,我们希望使用 `std.mem.eql(u8, a_key, b_key)`。 @@ -234,7 +234,7 @@ pub const StringContext = struct { }; ``` -## 自定义上下文 +## [自定义上下文]($section.id('自定义上下文')) 我们将在第一部分结束时,直接使用 `HashMap`,这意味着提供我们自己的上下文。我们将从一个简单的例子开始:为不区分大小写的 ASCII 字符串创建一个 `HashMap`。我们希望以下内容输出:`Goku = 9000`。请注意,虽然我们使用键 `GOKU` 进行插入,但我们使用“goku”进行获取: @@ -379,7 +379,7 @@ pub fn hash(_: HashContext, u: User) u64 { 插入这两个函数,以上示例应该可以工作。 -## 结论 +## [结论]($section.id('结论')) 希望你现在对 Zig 的哈希表的实现以及如何在代码中利用它们有了更好的理解。在大多数情况下,`std.StringHashMap` 或 `std.AutoHashMap` 就足够了。但知道 `*Unmanaged` 变体的存在和目的,以及更通用的 `std.HashMap`,可能会派上用场。如果没有其他用途,现在文档和它们的实现应该更容易理解了。 diff --git a/content/post/2024-06-11-zig-hashmap-2.smd b/content/post/2024-06-11-zig-hashmap-2.smd index e6ba02f..ef3f82e 100644 --- a/content/post/2024-06-11-zig-hashmap-2.smd +++ b/content/post/2024-06-11-zig-hashmap-2.smd @@ -41,7 +41,7 @@ keys: values: 这个基本的可视化表示将贯穿本文大部分内容,并且不断强调条目的位置需要保持一致性和可预测性。即使哈希表需要在增长时从一个底层数组移动到另一个(即当填充因子达到一定阈值并要求扩大以容纳更多数据时),这一事实是我们将反复回顾的主题。 -## 值管理 +## [值管理]($section.id('值管理')) 如果我们对上述代码片段进行扩展,并调用 `lookup.get("Paul")`,返回的值将是 `1234`。在处理像 `i32` 这样的原始类型时,很难直观地理解 `get` 方法和它的可选返回类型 `?i32` 或更通用的 `?V`(其中 `V` 表示任何值类型)之间的区别。考虑到这一点,让我们通过替换 `i32` 为一个封装了更多信息的 `User` 类型来展示这一概念: @@ -224,7 +224,7 @@ while (it.next()) |value_ptr| { 在最后一种情况下,由于我们存储的是 `User` 而不是 `*User`,我们的 `value_ptr` 是指向 `User` 的指针(不像之前那样是指向指针的指针)。 -## Keys +## [Keys]($section.id('Keys')) 我们可以开始和结束这一节:我们关于值的所有内容同样适用于键。这是100%正确的,但这在某种程度上不太直观。大多数开发人员很快就能理解,存储在哈希表中的堆分配的 `User` 实例有其自身的生命周期,需要显式管理/释放。但由于某些原因,这对于键来说并不那么明显。 @@ -337,7 +337,7 @@ if (lookup.fetchRemove(user.name)) |kv| { 对于大多数情况,在处理非原始键或值时,关键是当你调用哈希表的 `deinit` 时,你为键和值分配的任何内存不会被自动释放;你需要自己处理。 -## getOrPut +## [getOrPut]($section.id('getOrPut')) 虽然我们已经讨论过的内容有很多含义,但对我来说,直接暴露键和值指针的最大好处之一是 `getOrPut` 方法。 @@ -367,7 +367,7 @@ if (gop.found_existing) { 当然,只要不对哈希表进行修改,`value_ptr` 就应被视为有效。顺便提一句,这同样适用于我们通过 `iterator()`、`valueIterator` 和 `keyIterator` 获取的迭代键和值,原因相同。 -## 结论 +## [结论]($section.id('结论')) 希望你现在对使用「std.HashMap」、「std.AutoHashMap」和「std.StringHashMap」以及它们的「unmanaged」变体感到更加得心应手。虽然你可能永远不需要提供自己的上下文(例如「hash」和「eql」函数),但了解这是一个选项是有益的。在日常编程中,可视化数据尤其有用,尤其是在使用指针和添加间接层次时。每当我处理 `value_ptr` 或 `key_ptr` 时,我都会想到这些切片以及值或键与这些切片中值或键的实际地址之间的区别。 diff --git a/content/post/2024-08-12-zoop.smd b/content/post/2024-08-12-zoop.smd index a13e854..8d0794d 100644 --- a/content/post/2024-08-12-zoop.smd +++ b/content/post/2024-08-12-zoop.smd @@ -10,13 +10,13 @@ zoop 是 zig 的一个 OOP 解决方案,详细信息可以看看 [zoop官网](https://zhuyadong.github.io/zoop-docs/)。 -## 为什么不用别的 OOP 语言 +## [为什么不用别的 OOP 语言]($section.id('为什么不用别的 OOP 语言')) 简单的说,是我个人原因,必需使用 zig 的同时,还一定要用 OOP,所以有了 zoop。 -## zoop 入门 +## [zoop 入门]($section.id('zoop 入门')) -## 类和方法 +## [类和方法]($section.id('类和方法')) ```zig pub const Base = struct { @@ -73,7 +73,7 @@ pub const Base = struct { 上面的代码给 `Base` 添加了一个可以继承的方法 `getName()`。 -## 类的继承 +## [类的继承]($section.id('类的继承')) zoop 引入一个关键字 `extends` 用来实现继承,比如下面我们定义 `Base` 的子类 `Child`: @@ -102,7 +102,7 @@ test { } ``` -## 接口定义 +## [接口定义]($section.id('接口定义')) zoop 中的接口,实际上是一个胖指针。下面我们定义一个接口 `IGetName`: @@ -132,7 +132,7 @@ pub const IGetName = struct { 上面的代码具体原理下面会说到,这里大家知道接口就是这样定义的就行了。上面的代码定义了接口 `IGetName`,这个接口有一个方法 `getName()`。 -## 接口实现 +## [接口实现]($section.id('接口实现')) 上面的 `Base` 类正好也有个符合 `IGetName` 接口的方法 `getName()`,那我们修改一下 `Base` 的代码让它来实现 `IGetName` 接口: @@ -148,7 +148,7 @@ pub const Base = struct { 可以看到实现接口和继承用的同样一个关键字 `extends`。因为子类会继承父类的接口,所以这样一来,`Child` 也自动实现了 `IGetName` 接口。 -## 方法重写和虚函数调用 +## [方法重写和虚函数调用]($section.id('方法重写和虚函数调用')) 我们修改上面 `Child` 的代码,重写 `getName()` 方法: @@ -194,7 +194,7 @@ try t.expectEqualStrings(base.as(IGetName).?.getName(), "override"); 那么 zoop 的基本使用方法就介绍到这里,下面我们开始介绍 zoop 的实现原理。 -## 预设场景 +## [预设场景]($section.id('预设场景')) 接下来的讨论基于如下的属于 `mymod` 模块的类和接口: @@ -285,7 +285,7 @@ pub const Child = struct { - `Base`: 基类,实现接口 `ISetName` - `Child`: 子类,继承 `Base`,并实现接口 `IGetName` -## 核心数据结构 `zoop.Mixin(T)` +## [核心数据结构 `zoop.Mixin(T)`]($section.id('核心数据结构 `zoop.Mixin(T)`')) 我们看看两个类的 `mixin` 这个数据里面有什么: @@ -339,7 +339,7 @@ zoop.Mixin(Child) = struct { 上面两个函数获取的都是最外层对象的数据。根据对 `mixin` 数据的分析,zoop 的类型转换的原理就很清楚了,大家可以参考官网上关于 [类型转换](https://zhuyadong.github.io/zoop-docs/guide/as-cast) 的内容。 -## 动态构造类的方法、接口方法、和 `Vtable` +## [动态构造类的方法、接口方法、和 `Vtable`]($section.id('动态构造类的方法、接口方法、和 `Vtable`')) OOP 概念中的继承,重写,虚函数,实质其实就是在编译时动态构造需要的方法和属性。zoop 中主要是通过通过 [zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple) 这个模块来进行编译时动态构造。 @@ -347,11 +347,11 @@ OOP 概念中的继承,重写,虚函数,实质其实就是在编译时动 下面我介绍一下 zoop 中用到的 `comptime` 一些技巧,相信会对大家今后使用 zig 有帮助。 -## `struct` 很万能 +## [`struct` 很万能]($section.id('`struct` 很万能')) `comptime` 编程中,`struct` 是你最好的朋友,想在不同的 `comptime` 函数之间传递数据,最方便的方式,就是通过构造一个 `struct`,把想传递的数据通过 `pub const xxx = ...` 的方式传递出去,通过 `struct` 保存数据最好的地方,就在于这个数据在运行时也是可用的 (`struct` 中的常量,是保存在 exe 的 `.data` 区,运行时可见),[zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple) 就是通过这个方法实现的。 -## 动态构造 `struct` 的字段,用 `@Type()` +## [动态构造 `struct` 的字段,用 `@Type()`]($section.id('动态构造 `struct` 的字段,用 `@Type()`')) 网上好像很少有关于 `@Type()` 的使用说明,一般都是通过看 `zig.std` 的代码来学习,那我这里就稍微说明一下,希望能对大家有帮助。 目前 zig 通过 `@Type()`,能动态构造的 `struct`,只有纯字段类型的 `struct` (个人理解)。构造的方法,就是先把计算好的一个 `std.builtin.Type.StructField` 数组传递给 `@Type()` 来返回一个 `struct`,比如以下代码: @@ -398,7 +398,7 @@ const MyStruct = struct { zoop 动态构造 `Vtable` 就是通过这个方法做到的,参考 [zoop.DefVtable 原理](https://zhuyadong.github.io/zoop-docs/reference/principle#DefVtable) 和 [zoop 源代码](https://github.com/zhuyadong/zoop.git) -## 动态构造 `struct` 的函数,用 `usingnamespace` +## [动态构造 `struct` 的函数,用 `usingnamespace`]($section.id('动态构造 `struct` 的函数,用 `usingnamespace`')) 要想定义 `struct` 中的函数,理论上代码是一定要写在 `struct` 中的,目前 zig 唯一留下的一个口子,就是 `usingnamespace`,zoop 正是利用这个特性,来动态构造 `struct` 的函数。 @@ -442,7 +442,7 @@ pub usingnamespace struct { 因而实现了对 `Base.setName()` 方法的继承。 -## 运行时根据类型找 `Vtable` 和父类指针 +## [运行时根据类型找 `Vtable` 和父类指针]($section.id('运行时根据类型找 `Vtable` 和父类指针')) 这个功能的实现当时第一版是使用的 `std.StaticStringMap` 保存了一个类中所有接口名到接口 `Vtable` ,以及父类名到父类数据在本类中的地址偏移的映射。和 C++ 的 `dynamic_cast` 比起来,性能是比较差的。后来看到西瓜大大发的一个链接 [点这里](https://github.com/SuperAuguste/cursed-zig-errors),忽然意识到这不就是我一直想要的 `comptime` 全局变量么,我终于能写出 `typeId(comptime T: type) u32` 这样的函数了: diff --git a/content/post/2024-11-26-typed-fsm.smd b/content/post/2024-11-26-typed-fsm.smd index 3d8eac9..984d732 100644 --- a/content/post/2024-11-26-typed-fsm.smd +++ b/content/post/2024-11-26-typed-fsm.smd @@ -8,9 +8,9 @@ ## [typed fsm]($section.id('typed-fsm')) -## 1. 简单介绍类型化有限状态机的优势 +## [1. 简单介绍类型化有限状态机的优势]($section.id('1. 简单介绍类型化有限状态机的优势')) -## 1.1 介绍有限状态机 +## [1.1 介绍有限状态机]($section.id('1.1 介绍有限状态机')) 有限状态机(FSM,以下简称状态机)是程序中很常见的设计模式。 @@ -18,7 +18,7 @@ 而状态主要是在代码层面帮助人们理解消息的产生和处理。 -## 1.2 [typed-fsm-zig](https://github.com/sdzx-1/typed-fsm-zig) +## [1.2 [typed-fsm-zig](https://github.com/sdzx-1/typed-fsm-zig)]($section.id('1.2 [typed-fsm-zig](https://github.com/sdzx-1/typed-fsm-zig)')) typed-fsm-zig 是一个利用 zig 类型系统加一些编程规范实现的一个库,用于实现类型安全的有限状态机。 @@ -39,13 +39,13 @@ typed-fsm-zig 是一个利用 zig 类型系统加一些编程规范实现的一 在实际的使用中没有任何的代码生成,除了一处隐式的约束要求之外,没有任何其它的控制,开发者完全掌握状态机,因此你可以方便的将它和你现有的代码结合起来。 -## 2. 例子:修改 ATM 状态机的状态 +## [2. 例子:修改 ATM 状态机的状态]($section.id('2. 例子:修改 ATM 状态机的状态')) 这里我将以一个 ATM 状态机(以下简称 ATM)的例子来展示 typed-fsm-zig 和 zig 的类型系统如何帮助我快速修改 ATM 的状态。 为了简单性,这里我不展示构建 ATM 这个例子的过程,感兴趣的可以在这里看到[代码](https://github.com/sdzx-1/typed-fsm-zig/blob/master/examples/atm-gui.zig)。 -## 2.1 介绍 ATM 状态机 +## [2.1 介绍 ATM 状态机]($section.id('2.1 介绍 ATM 状态机')) ATM 代表自动取款机,因此它的代码的逻辑就是模拟自动取款机的一些行为:插入银行卡,输入 pin,检查 pin,取钱,修改 pin。 @@ -76,7 +76,7 @@ ATM 代表自动取款机,因此它的代码的逻辑就是模拟自动取款 接下来的文章中我将修改 Update 的行为,并展示在这个过程中类型系统如何帮助我快速调整代码。 -## 2.2 修改 Update 消息 +## [2.2 修改 Update 消息]($section.id('2.2 修改 Update 消息')) 实际的消息 Update 定义代码如下 @@ -134,7 +134,7 @@ referenced by: 在这里类型系统精确的告诉了我们需要修改的地方,以及原因。修改完成后程序即能正确运行。 -## 2.3 移除 changePin 状态 +## [2.3 移除 changePin 状态]($section.id('2.3 移除 changePin 状态')) 这一节中我们尝试移除 changePin 状态,看看类型系统会给我们什么反馈。 如果移除 changePin,新的状态图如下: @@ -177,7 +177,7 @@ examples/atm-gui.zig:296:10: error: no field named 'ChangePin' in enum '@typeInf 在这个过程中类型系统帮助我们找到问题和原因。这非常酷!!! -## 2.4 总结 +## [2.4 总结]($section.id('2.4 总结')) 以上是一个简单的例子,展示了 typed-fsm-zig 对于提升状态机编程体验的巨大效果。 @@ -194,7 +194,7 @@ examples/atm-gui.zig:296:10: error: no field named 'ChangePin' in enum '@typeInf -## 3. 原理与实现 +## [3. 原理与实现]($section.id('3. 原理与实现')) 最开始的版本是[typed-fsm](https://github.com/sdzx-1/typed-fsm),由使用 haskell 实现,它实现了完整类型安全的有限状态机。 @@ -434,7 +434,7 @@ fn s2Handler(val: Witness(Exmaple, .exit, .s2), ref: *i32) void { 以上就是 typed-fsm-zig 核心想法的完整介绍。接下来我将介绍需要的编程规范。 -## 4. typed-fsm-zig 需要哪些编程规范 +## [4. typed-fsm-zig 需要哪些编程规范]($section.id('4. typed-fsm-zig 需要哪些编程规范')) 1. 状态和消息集合之间需要满足的隐式命名规范 @@ -548,7 +548,7 @@ pub fn readyHandler(comptime w: AtmSt.EWitness(.ready), ist: *InternalState) voi 遵循这四点要求,就能获得强大的类型安全保证,足以让你愉快的使用状态机! -## 5. 接下来能够增强的功能 +## [5. 接下来能够增强的功能]($section.id('5. 接下来能够增强的功能')) 暂时我能想到的有如下几点: diff --git a/content/post/2025-01-23-bonkers-comptime.smd b/content/post/2025-01-23-bonkers-comptime.smd index d156dda..78a55fb 100644 --- a/content/post/2025-01-23-bonkers-comptime.smd +++ b/content/post/2025-01-23-bonkers-comptime.smd @@ -10,7 +10,7 @@ > 译注:原文中的代码块是交互式,翻译时并没有移植。另外,由于 comptime 本身即是关键概念,并且下文的意思更侧重于 Zig comptime 的特性,故下文大多使用 comptime 代替编译时概念。 -## 引子 +## [引子]($section.id('引子')) 编程通过自动化地处理数据极大地提升了生产力。而元编程则让我们可以像处理数据一样处理代码,以此将编程的力量反向作用于编程自身。而在底层编程中,我想元编程可能带来最大的优势,因为那些高级概念必须得精确映射到某些低级操作。然而,除了函数式编程语言外,我一直觉得各编程语言对元编程的实现并不理想。因此,当我看到 Zig 把元编程列为一个主要特性时,我提起了很大的兴趣。 @@ -20,7 +20,7 @@ 为了明确起见,所有示例都是有效的 Zig 代码,但示例中的转换只是概念性的,它们并不是 Zig 实际的实现方式。 -## 视角0: 忽略它 +## [视角0: 忽略它]($section.id('视角0: 忽略它')) 我说我喜欢这个特性,却又立刻叫你忽略它,这确实有点怪。但我认为此处正是 Zig comptime 威力所体现的地方,所以我将从这里出发。Zig Zen 中的第三条是“倾向于阅读代码,而不是编写代码。”确实,能够轻松地阅读代码在各种情况下都很重要,因为它是建立概念理解的基础,而这种理解也是调试或修改代码所必需的。 @@ -71,7 +71,7 @@ pub fn main() void { Zig 中有很多基于 comptime 且远远不止这样简单的类型反射,但你只需要阅读那些代码、完全无需深入了解其中有关 comptime 的细节就可以理解它们在干什么。当然,如果你想使用 comptime 编写代码,则不能仅仅止步于此,让我们继续深入。 -## 视角1: 泛型 +## [视角1: 泛型]($section.id('视角1: 泛型')) 泛型在 Zig 中并不是一个特定的功能。相反,Zig 中的仅仅一小部分的 comptime 特性就可以提供用来处理你进行泛型编程所需的一切。这种视角虽然不能让你完全理解 comptime,但它确实为你提供了一个入口点,借此,你可以完成基于元编程的许多任务。 @@ -121,7 +121,7 @@ pub fn main() void { 当然,也可以通过使用特殊类型 anytype 来推断参数的类型,而这通常在参数的类型对函数签名的其余部分没有影响时使用。(译注:此时要限制 a, b, c 的类型相同,所以此处不用 anytype ) -## 视角2:编译时运行的标准代码 +## [视角2:编译时运行的标准代码]($section.id('视角2:编译时运行的标准代码')) 这是一个古老的故事: 增加一种自动执行命令的方法。当然,你还需要变量。 哦,还有条件。 拜托,能给我循环吗?这些看似合理的需求,最终导致这些自动化命令变得越来越复杂,甚至演变成一个完整的宏语言。 但 Zig 不同, 在运行时、编译时,甚至是构建系统中都使用了相同的语言。 @@ -178,7 +178,7 @@ pub fn main() !void { comptime 和运行时之间有一些小的区别。比如,只有 comptime 可以访问类型为 comptime_int、comptime_float 或 type 的变量。此外,一些函数只有 comptime 参数,这使它们仅限于编译时环境。相对的,只有运行时才能进行系统调用和那些依赖系统调用的函数。如果你的代码不使用这些特性,那么它在编译时和运行时中的表现将是一样的。 -## 视角3:[程序特化](https://en.wikipedia.org/wiki/Partial_application) +## [视角3:[程序特化](https://en.wikipedia.org/wiki/Partial_application)]($section.id('视角3:[程序特化](https://en.wikipedia.org/wiki/Partial_application)')) > 译者注:程序特化(Partial Evaluation)是一种编译优化技术,主要是:在编译期预先计算部分表达式或代码路径,以减少运行时计算开销,提前生成更具体的代码实现。 @@ -255,7 +255,7 @@ onst MyStruct = struct { 上面的示例是我们手动展开后的示例,但这项工作是由 Zig 的 comptime 完成的。这使得我们可以直接独立而完整地编写出我们要实现的功能,而不需要添加"当你改变 `MyStruct` 的字段时,记得更新 sum 函数"这样的由于依赖于 `MyStruct` 具体字段而预防功能失效的注释。 基于 comptime 的版本在 `MyStruct` 的任何字段变更时都可以正确地自动处理。 -## 视角4:Comptime 求值,运行时代码生成 +## [视角4:Comptime 求值,运行时代码生成]($section.id('视角4:Comptime 求值,运行时代码生成')) 这与程序特化(Partial Evaluation)非常相似。这里有两个版本的代码,输入(编译前)和输出(编译后)。输入代码由编译器运行。如果一个语句在编译时是可知的,它就会被直接求值。但是如果一个语句需要某些运行时的值,那么这个语句就会被添加到输出代码中。 @@ -305,7 +305,7 @@ const MyStruct = struct { 这样做的另一个后果是,Zig 代码的静态分析要比大多数静态类型语言复杂得多,因为编译器需要运行很大一部分才能确定所有类型。 因此,在 Zig 工具链跟上之前,代码自动补全等编辑工具并不总是能很好地发挥作用。 -## 视角5:直接生成代码(Textual Code Generation) +## [视角5:直接生成代码(Textual Code Generation)]($section.id('视角5:直接生成代码(Textual Code Generation)')) 我在文章开头感叹元编程难度。然而,即使在 Zig 中,它仍然是一个强大的工具,在解决某些问题方面也占有一席之地。如果您熟悉这种元编程方法,对 Zig comptime 提供的功能可能会觉得有些残缺。比如, 怎么在写一段代码在运行时能够生成新代码? @@ -368,13 +368,13 @@ pub fn writeMyStructOfType( 与本节相关的是文本宏,如 C 语言中的文本宏。你可以做的大多数正常事情都可以在 comptime 中完成,尽管它们很少采用类似的形式。 不过,文本宏并不能做所有允许做的事情。 例如,你不能决定不喜欢某个 Zig 关键字,然后让宏代替你自己的关键字。 我认为这是一个正确的决定,尽管对于那些习惯了这种能力的人来说,这是一个艰难的过渡。 此外,Zig 参考了半个世纪以来的程序员在这方面的探索,所以它的选择要理智得多。 -## 结论 +## [结论]($section.id('结论')) 在阅读 Zig 代码以理解代码行为时,考虑 comptime 并不是必要的。而当编写 comptime 代码时,我通常会将其视为程序特化(Partial Evaluation)的一种形式。然而,如果你知道如何使用不同的元编程方法解决问题,你很可能有能力将其翻译成 comptime 形式。 元编程中直接生成代码的方法的存在,就是我全力支持 Zig 风格的 comptime 元编程的原因。尽管,直接生成代码是几乎是最强大的,但是,在阅读和调试时忽略 comptime 的特性的元编程方法确是最简单的。正因如此,我给本文取名为《Zig comptime 棒极了》。 -## 进一步阅读 +## [进一步阅读]($section.id('进一步阅读')) Zig 并非一个仅仅依赖 comptime 这一特性的语言。你可以在[官方网站](https://ziglang.org/)上了解更多关于 Zig 的信息。 diff --git a/content/post/news/2023-12-11-first-meetup.smd b/content/post/news/2023-12-11-first-meetup.smd index 05a19e2..c7202fe 100644 --- a/content/post/news/2023-12-11-first-meetup.smd +++ b/content/post/news/2023-12-11-first-meetup.smd @@ -45,7 +45,7 @@ Zig,目前主要有以下几个: 我们希望通过这些努力,提高 Zig 语言的知名度,完善 Zig 语言的生态,促进 Zig 语言的交流和学习。 -## 结论 +## [结论]($section.id('结论')) Zig 中文社区第一次线上会议的召开,标志着 Zig 社区正式启航。如果读者对共建社区感兴趣,欢迎与我们联系。 diff --git a/content/post/news/2023-12-27-second-meetup.smd b/content/post/news/2023-12-27-second-meetup.smd index 22826e7..be608a3 100644 --- a/content/post/news/2023-12-27-second-meetup.smd +++ b/content/post/news/2023-12-27-second-meetup.smd @@ -18,7 +18,7 @@ 这次会议主要是同步了之前会议落实的 action,主要是同步了不同项目的进展,由于临近年底,大家进度都不算太大,但还是有所进展,算是开了个好头😄 -## 项目进展 +## [项目进展]($section.id('项目进展')) ## [Zig-OS](https://github.com/zigcc/zig-os) @@ -33,7 +33,7 @@ action,主要是同步了不同项目的进展,由于临近年底,大家 - 增加了评论区的功能 - 待完成:反射(编译期反射和运行时反射)、内建函数说明(包含使用例子)、未定义行为、wasm、原子操作这些边缘部分 -## Zig 教学视频 +## [Zig 教学视频]($section.id('Zig 教学视频')) - 主要参与人员:Lambert - @@ -44,14 +44,14 @@ action,主要是同步了不同项目的进展,由于临近年底,大家 - 主要参与人员:夜白、西瓜 - 已经完成大部分内容 👍 -## Zig 构建系统教程 +## [Zig 构建系统教程]($section.id('Zig 构建系统教程')) - 主要参与人员:Reco - 目前主要是对 [zig build explained](https://zig.news/xq/zig-build-explained-part-3-1ima) 系列文章翻译 -## 新人介绍 +## [新人介绍]($section.id('新人介绍')) 在第一次会议后,有一些朋友想加入 ZigCC 社区,经过简单筛选,新增一名成员:Reco,下面是他的一些履历: diff --git a/content/post/news/2024-01-14-third-meetup.smd b/content/post/news/2024-01-14-third-meetup.smd index 299630d..4aa7143 100644 --- a/content/post/news/2024-01-14-third-meetup.smd +++ b/content/post/news/2024-01-14-third-meetup.smd @@ -18,7 +18,7 @@ - 公众号运营 - 如何与其他社区互动 -## 公众号运营 +## [公众号运营]($section.id('公众号运营')) 这是最近群里聊到的问题,由于 Zig 语言本身属于较新的技术,因此社区内资料比较少,这导致很多感兴趣的人没有一个好的学习途径。 @@ -40,12 +40,12 @@ 主要参与人员:西瓜、金中甲 -## 社区互动 +## [社区互动]($section.id('社区互动')) 目前我们的成员在 Zig 的实践方面相对较少,因此决定目前不过多的去宣传,在积攒了一些实际项目经验后,再来考虑。 -## 欢迎更多朋友加入 ZigCC +## [欢迎更多朋友加入 ZigCC]($section.id('欢迎更多朋友加入 ZigCC')) 现在回看,距离第一次 ZigCC 线上会议过了一个月,经过 ZigCC 成员的努力,还是交出了一份比较满意的答卷,[cookbook](https://github.com/zigcc/zig-cookbook) From 1b9bebf84d4185bc92b91029eb4639db5fab63d9 Mon Sep 17 00:00:00 2001 From: xihale Date: Wed, 2 Jul 2025 10:23:08 +0800 Subject: [PATCH 15/22] Standardize section titles and improve content structure across multiple SMD files. Update headings to ensure consistent formatting and enhance navigation with appropriate links. Remove obsolete files and adjust layout templates for better organization and clarity. --- content/about.smd | 4 +-- content/community.smd | 2 +- content/contributing.smd | 6 ++-- ...10-coding-in-zig.smd => coding-in-zig.smd} | 18 +++++----- .../{11-conclusion.smd => conclusion.smd} | 2 +- .../learn/{09-generics.smd => generics.smd} | 2 +- .../{08-heap-memory.smd => heap-memory.smd} | 20 +++++------ content/learn/index.smd | 34 +++++++++---------- ...-installing-zig.smd => installing-zig.smd} | 4 +-- ...overview-1.smd => language-overview-1.smd} | 18 +++++----- ...overview-2.smd => language-overview-2.smd} | 14 ++++---- .../learn/{06-pointers.smd => pointers.smd} | 12 +++---- content/learn/{01-preface.smd => preface.smd} | 0 .../{07-stack-memory.smd => stack-memory.smd} | 6 ++-- .../{05-style-guide.smd => style-guide.smd} | 8 ++--- content/monthly/202207.smd | 6 ++-- content/monthly/202208.smd | 6 ++-- content/monthly/202209.smd | 12 +++---- content/monthly/202210.smd | 6 ++-- content/monthly/202211.smd | 10 +++--- content/monthly/202212.smd | 6 ++-- content/monthly/202301.smd | 8 ++--- content/monthly/202302.smd | 8 ++--- content/monthly/202303.smd | 8 ++--- content/monthly/202304.smd | 8 ++--- content/monthly/202305.smd | 8 ++--- content/monthly/202306.smd | 10 +++--- content/monthly/202307.smd | 8 ++--- content/monthly/202308.smd | 32 ++++++++--------- content/monthly/202309.smd | 12 +++---- content/monthly/202310.smd | 10 +++--- content/monthly/202311.smd | 8 ++--- content/monthly/202402.smd | 8 ++--- content/monthly/202403.smd | 8 ++--- content/monthly/202404.smd | 8 ++--- content/monthly/202405.smd | 14 ++++---- content/monthly/202406.smd | 16 ++++----- content/monthly/202407.smd | 16 ++++----- content/monthly/202410.smd | 32 ++++++++--------- content/monthly/202411.smd | 14 ++++---- content/post/0.14.smd | 24 ++++++------- content/post/2023-09-05-bog-gc-1-en.smd | 16 ++++----- content/post/2023-09-05-bog-gc-1.smd | 14 ++++---- content/post/2023-09-05-hello-world.smd | 4 +-- content/post/2023-09-21-zig-midi.smd | 26 +++++++------- .../2023-12-24-zig-build-explained-part1.smd | 16 ++++----- .../2023-12-28-zig-build-explained-part2.smd | 34 +++++++++---------- .../2023-12-29-zig-build-explained-part3.smd | 30 ++++++++-------- ...2-how-to-release-your-zig-applications.smd | 14 ++++---- content/post/2024-04-06-zig-cpp.smd | 8 ++--- content/post/2024-05-07-package-hash.smd | 22 ++++++------ content/post/2024-05-24-interface-idioms.smd | 14 ++++---- content/post/2024-06-10-zig-hashmap-1.smd | 8 ++--- content/post/2024-06-11-zig-hashmap-2.smd | 8 ++--- content/post/2024-08-12-zoop.smd | 30 ++++++++-------- content/post/2024-11-26-typed-fsm.smd | 24 ++++++------- content/post/2025-01-23-bonkers-comptime.smd | 18 +++++----- content/post/news/2023-12-11-first-meetup.smd | 2 +- .../post/news/2023-12-27-second-meetup.smd | 14 ++++---- content/post/news/2024-01-14-third-meetup.smd | 6 ++-- layouts/learn.shtml | 4 +++ layouts/post.shtml | 4 --- layouts/templates/content.shtml | 4 +++ 63 files changed, 389 insertions(+), 387 deletions(-) rename content/learn/{10-coding-in-zig.smd => coding-in-zig.smd} (98%) rename content/learn/{11-conclusion.smd => conclusion.smd} (98%) rename content/learn/{09-generics.smd => generics.smd} (99%) rename content/learn/{08-heap-memory.smd => heap-memory.smd} (97%) rename content/learn/{02-installing-zig.smd => installing-zig.smd} (93%) rename content/learn/{03-language-overview-1.smd => language-overview-1.smd} (98%) rename content/learn/{04-language-overview-2.smd => language-overview-2.smd} (98%) rename content/learn/{06-pointers.smd => pointers.smd} (98%) rename content/learn/{01-preface.smd => preface.smd} (100%) rename content/learn/{07-stack-memory.smd => stack-memory.smd} (98%) rename content/learn/{05-style-guide.smd => style-guide.smd} (94%) diff --git a/content/about.smd b/content/about.smd index ddcea50..44bcdce 100644 --- a/content/about.smd +++ b/content/about.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [About Zine]($section.id('About Zine')) +# [About Zine]($section.id('About Zine')) Zine is an MIT-licensed project created by [Loris Cro](https://kristoff.it) and other contributors listed on the [official repository](https://github.com/kristoff-it/zine/contributors). @@ -34,7 +34,7 @@ of authoring languages: ># [NOTE]($block) >The correct file extension for SuperMD pages is `.smd`. -## [Zine is alpha software]($section.id('Zine is alpha software')) +# [Zine is alpha software]($section.id('zine-is-alpha-software')) Zine is not yet complete. The main functionality is present and you will be able to build even moderately complex static websites without issue. diff --git a/content/community.smd b/content/community.smd index 3c6c83f..a1c7146 100644 --- a/content/community.smd +++ b/content/community.smd @@ -8,7 +8,7 @@ TODO: issue [让我们一起探索 Zig 的魅力,推动 Zig 在中文社区内的发展!]($text.attrs('center','large','bold')) -## [网站更新日志]($section.id('网站更新日志')) +# [网站更新日志]($section.id('website-update-log')) - **2025-06-30:** 切换到 [zine](https://zine-ssg.io/) - **2024-08-18:** 切换主题 [docsy](https://github.com/google/docsy) diff --git a/content/contributing.smd b/content/contributing.smd index 2d15fc2..3c958ed 100644 --- a/content/contributing.smd +++ b/content/contributing.smd @@ -12,7 +12,7 @@ Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文 2. 改进 zigcc 组织下的开源项目,这是 [open issues](https://github.com/search?q=state%3Aopen+org%3Azigcc++NOT+%E6%97%A5%E6%8A%A5&type=issues&ref=advsearch) 3. 参与不定期的线上会议 TODO -## [供稿方式]($section.id('供稿方式')) +# [供稿方式]($section.id('供稿方式')) 1. Fork 仓库 https://github.com/zigcc/zigcc.github.io 2. 在 `content/post` 内添加自己的文章(md 或 org 格式均可),文件命名为: `${YYYY}-${MM}-${DD}-${SLUG}.md` @@ -26,13 +26,13 @@ date: '2023-09-05T16:13:13+0800' --- ``` -## [本地预览]($section.id('本地预览')) +# [本地预览]($section.id('本地预览')) TODO ```bash zine ``` -## [发布平台]($section.id('发布平台')) +# [发布平台]($section.id('publishing-platform')) - [ZigCC 网站](https://ziglang.cc) - [ZigCC 公众号](https://github.com/zigcc/.github/raw/main/zig_mp.png) diff --git a/content/learn/10-coding-in-zig.smd b/content/learn/coding-in-zig.smd similarity index 98% rename from content/learn/10-coding-in-zig.smd rename to content/learn/coding-in-zig.smd index 5a6530f..3b6de98 100644 --- a/content/learn/10-coding-in-zig.smd +++ b/content/learn/coding-in-zig.smd @@ -1,6 +1,6 @@ --- .title = "实战", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2024-01-10T00:00:00"), .author = "Karl Seguin; ZigCC", .layout = "learn.shtml", .draft = false, @@ -10,7 +10,7 @@ 在介绍了 Zig 语言的大部分内容之后,我们将对一些主题进行回顾,并展示几种使用 Zig 编程时一些实用的技巧。在此过程中,我们将介绍更多的标准库,并介绍一些稍复杂些的代码片段。 -## [悬空指针 Dangling Pointers]($section.id('dangling-pointers')) +# [悬空指针 Dangling Pointers]($section.id('dangling-pointers')) 我们首先来看看更多关于悬空指针的例子。这似乎是一个奇怪的问题,但如果你之前主要使用带垃圾回收的语言,这可能是你学习 Zig 最大的障碍。 @@ -105,7 +105,7 @@ const User = struct { 如果把所有东西都放在一个函数中,再加上一个像 `User` 这样的小值,这仍然像是一个人为制造的问题。我们需要一个能让数据所有权成为当务之急的例子。 -## [所有权 Ownership]($section.id('ownership')) +# [所有权 Ownership]($section.id('ownership')) 我喜欢哈希表(HashMap),因为这是每个人都知道并且会经常使用的结构。它们有很多不同的用例,其中大部分你可能都用过。虽然哈希表可以用在一个短期查找的地方,但通常用于长期查找,因此插入其内的值需要同样长的生命周期。 @@ -218,7 +218,7 @@ defer { 我保证,关于悬挂指针和内存管理的讨论已经结束了。我们所讨论的内容可能还不够清晰或过于抽象。当你有更实际的问题需要解决时,再重新讨论这个问题也不迟。不过,如果你打算编写任何稍具规模(non-trivial)的程序,这几乎肯定是你需要掌握的内容。当你觉得可以的时候,我建议你参考上面这个示例,并自己动手实践一下。引入一个 `UserLookup` 类型来封装我们必须做的所有内存管理。尝试使用 `*User` 代替 `User`,在堆上创建用户,然后像处理键那样释放它们。编写覆盖新结构的测试,使用 `std.testing.allocator` 确保不会泄漏任何内存。 -## [ArrayList]($section.id('arraylist')) +# [ArrayList]($section.id('arraylist')) 现在你可以忘掉我们的 `IntList` 和我们创建的通用替代方案了。Zig 标准库中有一个动态数组实现:`std.ArrayList(T)`。 @@ -313,9 +313,9 @@ pub fn main() !void { } ``` -## [Anytype]($section.id('anytype')) +# [Anytype]($section.id('anytype')) -在[语言概述的第一部分](03-language-overview-1)中,我们简要介绍了 `anytype`。这是一种非常有用的编译时 duck 类型。下面是一个简单的 logger: +在[语言概述的第一部分](language-overview-1)中,我们简要介绍了 `anytype`。这是一种非常有用的编译时 duck 类型。下面是一个简单的 logger: ```zig pub const Logger = struct { @@ -383,7 +383,7 @@ fn stringify( 第一个参数 `value: anytype` 是显而易见的,它是要序列化的值,可以是任何类型(实际上,Zig 的 JSON 序列化器不能序列化某些类型,比如 HashMap)。我们可以猜测,`out_stream` 是写入 JSON 的地方,但至于它需要实现什么方法,你和我一样猜得到。唯一的办法就是阅读源代码,或者传递一个假值,然后使用编译器错误作为我们的文档。如果有更好的自动文档生成器,这一点可能会得到改善。不过,我希望 Zig 能提供接口,这已经不是第一次了。 -## [@TypeOf]($section.id('typeof')) +# [@TypeOf]($section.id('typeof')) 在前面的部分中,我们使用 `@TypeOf` 来帮助我们检查各种变量的类型。从我们的用法来看,你可能会认为它返回的是字符串类型的名称。然而,鉴于它是一个 PascalCase 风格函数,你应该更清楚:它返回的是一个 `type`。 @@ -412,7 +412,7 @@ pub const User = struct { 更常见的是 `@TypeOf` 与 `@typeInfo` 配对,后者返回一个 `std.builtin.Type`。这是一个功能强大的带标签的联合(tagged union),可以完整描述一个类型。`std.json.stringify` 函数会递归地调用它,以确定如何将提供的 `value` 序列化。 -## [构建系统]($section.id('build-system')) +# [构建系统]($section.id('build-system')) 如果你通读了整本指南,等待着深入了解如何建立更复杂的项目,包括多个依赖关系和各种目标,那你就要失望了。Zig 拥有强大的构建系统,以至于越来越多的非 Zig 项目都在使用它,比如 libsodium。不幸的是,所有这些强大的功能都意味着,对于简单的需求来说,它并不是最容易使用或理解的。 @@ -497,7 +497,7 @@ test "dummy build test" { 这是启动和运行构建系统所需的最低配置。但是请放心,如果你需要构建你的程序,Zig 内置的功能大概率能覆盖你的需求。最后,你可以(也应该)在你的项目根目录下使用 `zig init`,让 Zig 为你创建一个文档齐全的 `build.zig` 文件。 -## [第三方依赖]($section.id('third-party-deps')) +# [第三方依赖]($section.id('third-party-dependencies')) Zig 的内置软件包管理器相对较新,因此存在一些缺陷。虽然还有改进的余地,但它目前还是可用的。我们需要了解两个部分:创建软件包和使用软件包。我们将对其进行全面介绍。 diff --git a/content/learn/11-conclusion.smd b/content/learn/conclusion.smd similarity index 98% rename from content/learn/11-conclusion.smd rename to content/learn/conclusion.smd index 8f35a2b..5f33459 100644 --- a/content/learn/11-conclusion.smd +++ b/content/learn/conclusion.smd @@ -1,6 +1,6 @@ --- .title = "总结", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2024-01-11T00:00:00"), .author = "Karl Seguin; ZigCC", .layout = "learn.shtml", .draft = false, diff --git a/content/learn/09-generics.smd b/content/learn/generics.smd similarity index 99% rename from content/learn/09-generics.smd rename to content/learn/generics.smd index a65d260..6754604 100644 --- a/content/learn/09-generics.smd +++ b/content/learn/generics.smd @@ -1,6 +1,6 @@ --- .title = "泛型", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2024-01-09T00:00:00"), .author = "Karl Seguin; ZigCC", .layout = "learn.shtml", .draft = false, diff --git a/content/learn/08-heap-memory.smd b/content/learn/heap-memory.smd similarity index 97% rename from content/learn/08-heap-memory.smd rename to content/learn/heap-memory.smd index 6a23bc3..6a8ae6a 100644 --- a/content/learn/08-heap-memory.smd +++ b/content/learn/heap-memory.smd @@ -1,6 +1,6 @@ --- .title = "堆内存", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2024-01-08T00:00:00"), .author = "Karl Seguin; ZigCC", .layout = "learn.shtml", .draft = false, @@ -14,7 +14,7 @@ 本部分分为两个主题。第一个主题是第三个内存区域--堆的总体概述。另一个主题是 Zig 直接而独特的堆内存管理方法。即使你熟悉堆内存,比如使用过 C 语言的 `malloc`,你也会希望阅读第一部分,因为它是 Zig 特有的。 -## [堆]($section.id('dui')) +# [堆]($section.id('heap')) 堆是我们可以使用的第三个也是最后一个内存区域。与全局数据和调用栈相比,堆有点像蛮荒之地:什么都可以使用。具体来说,在堆中,我们可以在运行时创建大小已知的内存,并完全控制其生命周期。 @@ -52,7 +52,7 @@ fn getRandomCount() !u8 { 一般来说,每次 `alloc` 都会有相应的 `free`。`alloc`分配内存,`free`释放内存。不要让这段简单的代码限制了你的想象力。这种 `try alloc` + `defer free` 的模式很常见,这是有原因的:在我们分配内存的地方附近释放相对来说是万无一失的。但同样常见的是在一个地方分配,而在另一个地方释放。正如我们之前所说,堆没有内置的生命周期管理。你可以在 HTTP 处理程序中分配内存,然后在后台线程中释放,这是代码中两个完全独立的部分。 -## [defer 和 errdefer]($section.id('defer 和 errdefer')) +# [defer 和 errdefer]($section.id('defer-and-errdefer')) 说句题外话,上面的代码介绍了一个新的语言特性:`defer`,它在退出作用域时执行给定的代码。『作用域退出』包括到达作用域的结尾或从作用域返回。严格来说, `defer` 与分配器或内存管理并无严格关系;你可以用它来执行任何代码。但上述用法很常见。 @@ -100,7 +100,7 @@ pub const Game = struct { > `init` 和 `deinit` 的名字并不特殊。它们只是 Zig 标准库使用的,也是社区采纳的名称。在某些情况下,包括在标准库中,会使用 `open` 和 `close`,或其他更适当的名称。 -## [双重释放和内存泄漏]($section.id('双重释放和内存泄漏')) +# [双重释放和内存泄漏]($section.id('double-free-and-memory-leak')) 上面提到过,没有规则规定什么时候必须释放什么东西。但事实并非如此,还是有一些重要规则,只是它们不是强制的,需要你自己格外小心。 @@ -159,7 +159,7 @@ fn isSpecial(allocator: Allocator, name: [] const u8) !bool { 至少在双重释放的情况下,我们的程序会遭遇严重崩溃。内存泄漏可能很隐蔽。不仅仅是根本原因难以确定。真正的小泄漏或不常执行的代码中的泄漏甚至很难被发现。这是一个很常见的问题,Zig 提供了帮助,我们将在讨论分配器时看到。 -## [创建与销毁]($section.id('创建与销毁')) +# [创建与销毁]($section.id('create-and-destroy')) `std.mem.Allocator`的`alloc`方法会返回一个切片,其长度为传递的第二个参数。如果想要单个值,可以使用 `create` 和 `destroy` 而不是 `alloc` 和 `free`。 @@ -233,7 +233,7 @@ fn init(allocator: std.mem.Allocator, id: u64, power: i32) !*User{ 请记住,`create` 返回一个 `!*User`,所以我们的 `user` 是 `*User` 类型。 -## [分配器 Allocator]($section.id('分配器 Allocator')) +# [分配器 Allocator]($section.id('allocator')) Zig 的核心原则之一是无隐藏内存分配。根据你的背景,这听起来可能并不特别。但这与 C 语言中使用标准库的 malloc 函数分配内存的做法形成了鲜明的对比。在 C 语言中,如果你想知道一个函数是否分配内存,你需要阅读源代码并查找对 malloc 的调用。 @@ -254,7 +254,7 @@ defer allocator.free(say); 如果你正在构建一个库,那么最好接受一个 `std.mem.Allocator`,然后让库的用户决定使用哪种分配器实现。否则,你就需要选择正确的分配器,正如我们将看到的,这些分配器并不相互排斥。在你的程序中创建不同的分配器可能有很好的理由。 -## [通用分配器 GeneralPurposeAllocator]($section.id('通用分配器 GeneralPurposeAllocator')) +# [通用分配器 GeneralPurposeAllocator]($section.id('general-purpose-allocator')) 顾名思义,`std.heap.GeneralPurposeAllocator` 是一种通用的、线程安全的分配器,可以作为应用程序的主分配器。对于许多程序来说,这是唯一需要的分配器。程序启动时,会创建一个分配器并传递给需要它的函数。我的 HTTP 服务器库中的示例代码就是一个很好的例子: @@ -299,7 +299,7 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 类型是什么,字段在哪里?类型其实是 `std.heap.general_purpose_allocator.Config`,但它并没有直接暴露出来,这也是我们没有显式给出类型的原因之一。没有设置字段是因为 Config 结构定义了默认值,我们将使用默认值。这是配置、选项的中常见的模式。事实上,我们在下面几行向 `init` 传递 `.{.port = 5882}` 时又看到了这种情况。在本例中,除了端口这一个字段外,我们都使用了默认值。 -## [std.testing.allocator]($section.id('std.testing.allocator')) +# [std.testing.allocator]($section.id('std-testing-allocator')) 希望当我们谈到内存泄漏时,你已经足够烦恼,而当我提到 Zig 可以提供帮助时,你肯定渴望了解更多这方面内容。这种帮助来自 `std.testing.allocator`,它是一个 `std.mem.Allocator` 实现。目前,它基于通用分配器(GeneralPurposeAllocator)实现,并与 Zig 的测试运行器进行了集成,但这只是实现细节。重要的是,如果我们在测试中使用 `std.testing.allocator`,就能捕捉到大部分内存泄漏。 @@ -423,7 +423,7 @@ self.allocator.free(self.items); 将`items`复制到我们的 `larger` 切片中后, 添加最后一行`free`可以解决泄漏的问题。如果运行 `zig test learning.zig`,便不会再有错误。 -## [ArenaAllocator]($section.id('ArenaAllocator')) +# [ArenaAllocator]($section.id('arena-allocator')) 通用分配器(GeneralPurposeAllocator)是一个合理的默认设置,因为它在所有可能的情况下都能很好地工作。但在程序中,你可能会遇到一些固定场景,使用更专业的分配器可能会更合适。其中一个例子就是需要在处理完成后丢弃的短期状态。解析器(Parser)通常就有这样的需求。一个 `parse` 函数的基本轮廓可能是这样的 @@ -508,7 +508,7 @@ defer list.deinit(); 最后举个简单的例子,我上面提到的 HTTP 服务器在响应中暴露了一个 `ArenaAllocator`。一旦发送了响应,它就会被清空。由于`ArenaAllocator`的生命周期可以预测(从请求开始到请求结束),因此它是一种高效的选择。就性能和易用性而言,它都是高效的。 -## [固定缓冲区分配器 FixedBufferAllocator]($section.id('固定缓冲区分配器 FixedBufferAllocator')) +# [固定缓冲区分配器 FixedBufferAllocator]($section.id('fixed-buffer-allocator')) 我们要讨论的最后一个分配器是 `std.heap.FixedBufferAllocator`,它可以从我们提供的缓冲区(即 `[]u8`)中分配内存。这种分配器有两大好处。首先,由于所有可能使用的内存都是预先创建的,因此速度很快。其次,它自然而然地限制了可分配内存的数量。这一硬性限制也可以看作是一个缺点。另一个缺点是,`free` 和 `destroy` 只对最后分配/创建的项目有效(想想堆栈)。调用释放非最后分配的内存是安全的,但不会有任何作用。 diff --git a/content/learn/index.smd b/content/learn/index.smd index b4ce493..d5d6963 100644 --- a/content/learn/index.smd +++ b/content/learn/index.smd @@ -6,25 +6,25 @@ .draft = false, --- -## [《学习 Zig》 目录]($section.id('table-of-contents')) - -- [前言](./01-preface) -- [安装 Zig](./02-installing-zig) -- [语言概述 - 第一部分](./03-language-overview-1) -- [语言概述 - 第二部分](./04-language-overview-2) -- [编码风格](./05-style-guide) -- [指针](./06-pointers) -- [栈内存](./07-stack-memory) -- [堆内存和分配器](./08-heap-memory) -- [泛型](./09-generics) -- [实战](./10-coding-in-zig) -- [总结](./11-conclusion) +# [《学习 Zig》 目录]($section.id('table-of-contents')) + +- [前言](./preface) +- [安装 Zig](./installing-zig) +- [语言概述 - 第一部分](./language-overview-1) +- [语言概述 - 第二部分](./language-overview-2) +- [编码风格](./style-guide) +- [指针](./pointers) +- [栈内存](./stack-memory) +- [堆内存和分配器](./heap-memory) +- [泛型](./generics) +- [实战](./coding-in-zig) +- [总结](./conclusion) [《学习 Zig》](https://www.openmymind.net/learning_zig/)系列教程最初由 [Karl Seguin](https://github.com/karlseguin) 编写,该教程行文流畅,讲述的脉络由浅入深,深入浅出,是入门 Zig 非常不错的选择。因此,[Zig 中文社区](https://ziglang.cc)将其翻译成中文,便于在中文用户内阅读与传播。 初次接触 Zig 的用户可以按序号依次阅读,对于有经验的 Zig 开发者可按需阅读感兴趣的章节。 -## [关于原作者]($section.id('关于原作者')) +# [关于原作者]($section.id('about-original-author')) [Karl Seguin](https://www.linkedin.com/in/karlseguin/) 在多个领域有着丰富经验,前微软 MVP,他撰写了大量文章,是多个微软公共新闻组的活跃成员。现居新加坡。他还是以下教程的作者: @@ -34,13 +34,13 @@ 可以在 找到他的博客,或者通过 [@karlseguin](http://twitter.com/karlseguin) 在 Twitter 上关注他。 -## [翻译原则]($section.id('翻译原则')) +# [翻译原则]($section.id('translation-principles')) 技术文档的翻译首要原则是准确,但在准确的前提下如何保证『信、达、雅』?这是个挑战,在翻译本教程时,在某些情况下会根据上下文进行意译,便于中文读者阅读。 最后,感谢翻译者的无私贡献。❤️️ -## [离线阅读]($section.id('离线阅读')) +# [离线阅读]($section.id('offline-reading')) 在本仓库的 [release 页面](https://github.com/zigcc/zigcc.github.io/releases)会定期将本教程导出为 PDF 格式,读者可按需下载。 @@ -49,7 +49,7 @@ ``` -## [其他学习资料]($section.id('其他学习资料')) +# [其他学习资料]($section.id('other-learning-resources')) 由于 Zig 目前还处于快速迭代,因此最权威的资料无疑是官方的 [Zig Language Reference](https://ziglang.org/documentation/master/),遇到语言的细节问题,基本都可以在这里找到答案。 其次是社区的一些高质量教程,例如: diff --git a/content/learn/02-installing-zig.smd b/content/learn/installing-zig.smd similarity index 93% rename from content/learn/02-installing-zig.smd rename to content/learn/installing-zig.smd index 9e0a706..2ea761d 100644 --- a/content/learn/02-installing-zig.smd +++ b/content/learn/installing-zig.smd @@ -1,6 +1,6 @@ --- .title = "安装 Zig", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2024-01-02T00:00:00"), .author = "Karl Seguin; ZigCC", .layout = "learn.shtml", .draft = false, @@ -25,7 +25,7 @@ EOF asdf plugin-add zig https://github.com/zigcc/asdf-zig.git -## [安装最新版]($section.id('安装最新版')) +# [安装最新版]($section.id('install-latest-version')) asdf install zig latest asdf global zig latest zig version diff --git a/content/learn/03-language-overview-1.smd b/content/learn/language-overview-1.smd similarity index 98% rename from content/learn/03-language-overview-1.smd rename to content/learn/language-overview-1.smd index ce227bd..76ce6b0 100644 --- a/content/learn/03-language-overview-1.smd +++ b/content/learn/language-overview-1.smd @@ -1,6 +1,6 @@ --- .title = "语言概述 - 第一部分", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2024-01-03T00:00:00"), .author = "Karl Seguin; ZigCC", .layout = "learn.shtml", .draft = false, @@ -35,9 +35,9 @@ pub const User = struct { 这是一个简单的示例,即使你是第一次看到 Zig,大概率能够看懂这段代码。尽管如此,下面的内容我们还是来逐行分析它。 -> 请参阅[安装 Zig 部分](02-installing-zig),以便快速启动并运行它。 +> 请参阅[安装 Zig 部分](installing-zig),以便快速启动并运行它。 -## [模块引用]($section.id('module-reference')) +# [模块引用]($section.id('module-reference')) 很少有程序是在没有标准库或外部库的情况下以单个文件编写的。我们的第一个程序也不例外,它使用 Zig 的标准库来进行打印输出。 Zig 的模块系统非常简单,只依赖于 `@import` 函数和 `pub` 关键字(使代码可以在当前文件外部访问)。 @@ -88,7 +88,7 @@ const MAX_POWER = user.MAX_POWER; - 如何导入其他文件 - 如何导出变量、函数定义 -## [代码注释]($section.id('code-comment')) +# [代码注释]($section.id('code-comment')) 下面这行 Zig 代码是一个注释: @@ -100,7 +100,7 @@ Zig 没有像 C 语言中类似 `/* ... */` 的多行注释。 基于注释的文档自动生成功能正在试验中。如果你看过 Zig 的标准库文档,你就会看到它的实际应用。`//!` 被称为顶级文档注释,可以放在文件的顶部。三斜线注释 (`///`) 被称为文档注释,可以放在特定位置,如声明之前。如果在错误的地方使用这两种文档注释,编译器都会出错。 -## [函数]($section.id('function')) +# [函数]($section.id('function')) 下面这行 Zig 代码是程序的入口函数 `main`: @@ -146,7 +146,7 @@ fn add(a: i64, b: i64) i64 { 为了提高可读性,Zig 中不支持函数重载(用不同的参数类型或参数个数定义的同名函数)。暂时来说,以上就是我们需要了解的有关函数的全部内容。 -## [结构体]($section.id('struct')) +# [结构体]($section.id('struct')) 下面这行代码创建了一个 `User` 结构体: @@ -266,7 +266,7 @@ pub fn init(name: []const u8, power: u64) User { 就像我们迄今为止已经探索过的大多数东西一样,今后在讨论 Zig 语言的其他部分时,我们会再次讨论结构体。不过,在大多数情况下,它们都是简单明了的。 -## [数组和切片]($section.id('array-slice')) +# [数组和切片]($section.id('array-slice')) 我们可以略过代码的最后一行,但鉴于我们的代码片段包含两个字符串 `"Goku"` 和 `{s}'s power is {d}\n`,你可能会对 Zig 中的字符串感到好奇。为了更好地理解字符串,我们先来了解一下数组和切片。 @@ -365,7 +365,7 @@ pub fn main() void { 在了解 Zig 语言的其他方面(尤其是字符串)的同时,我们还将发现更多有关数组和切片的知识。 -## [字符串]($section.id('string')) +# [字符串]($section.id('string')) 我希望我能说,Zig 里有字符串类型,而且非常棒。遗憾的是,它没有。最简单来说,字符串是字节(u8)的序列(即数组或切片)。实际上,我们可以从 `name` 字段的定义中看到这一点:`name: []const u8`. @@ -399,7 +399,7 @@ pub fn main() void { 当然,在实际程序中,大多数字符串(以及更通用的数组)在编译时都是未知的。最典型的例子就是用户输入,程序编译时并不知道用户输入。这一点我们将在讨论内存时再次讨论。但简而言之,对于这种在编译时不能确定值的数据(长度当然也就无从得知),我们将在运行时动态分配内存。我们的字符串变量(仍然是 `[]const u8` 类型)将是指向动态分配的内存的切片。 -## [comptime 和 anytype]($section.id('comptime-anytype')) +# [comptime 和 anytype]($section.id('comptime-and-anytype')) 在我们未解释的最后一行代码中,涉及的知识远比表面看到的多: diff --git a/content/learn/04-language-overview-2.smd b/content/learn/language-overview-2.smd similarity index 98% rename from content/learn/04-language-overview-2.smd rename to content/learn/language-overview-2.smd index c294bec..ad735aa 100644 --- a/content/learn/04-language-overview-2.smd +++ b/content/learn/language-overview-2.smd @@ -1,6 +1,6 @@ --- .title = "语言概述 - 第二部分", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2024-01-04T00:00:00"), .author = "Karl Seguin; ZigCC", .layout = "learn.shtml", .draft = false, @@ -10,7 +10,7 @@ 本部分继续上一部分的内容:熟悉 Zig 语言。我们将探究 Zig 的控制流和结构以外的类型。通过这两部分的学习,我们将掌握 Zig 语言的大部分语法,这让我们可以继续深入 Zig 语言,同时也为如何使用 std 标准库打下了基础。 -## [控制流]($section.id('kongzhi-liu')) +# [控制流]($section.id('control-flow')) Zig 的控制流很可能是我们所熟悉的,但它与 Zig 语言的其他特性协同工作是我们还没有探索过。我们先简单概述控制流的基本使用,之后在讨论依赖控制流的相关特性时,再来重新回顾。 @@ -207,7 +207,7 @@ const personality_analysis = blk: { 稍后,当我们讨论带标签的联合(tagged union)、错误联合(error unions)和可选类型(Optional)时,我们将看到控制流如何与它们联合使用。 -## [枚举]($section.id('meiju')) +# [枚举]($section.id('enum')) 枚举是带有标签的整数常量。它们的定义很像结构体: @@ -241,7 +241,7 @@ const Stage = enum { `switch` 的穷举性质使它能与枚举很好地搭配,因为它能确保你处理了所有可能的情况。不过在使用 `switch` 的 `else` 子句时要小心,因为它会匹配任何新添加的枚举值,而这可能不是我们想要的行为。 -## [带标签的联合 Tagged Union]($section.id('tagged-union')) +# [带标签的联合 Tagged Union]($section.id('tagged-union')) 联合定义了一个值可以具有的一系列类型。例如,这个 `Number` 可以是整数、浮点数或 nan(非数字): @@ -320,7 +320,7 @@ const Timestamp = union(enum) { 这里 Zig 会根据带标签的联合,自动创建一个隐式枚举。 -## [可选类型 Optional]($section.id('optional')) +# [可选类型 Optional]($section.id('optional')) 在类型前加上问号 `?`,任何值都可以声明为可选类型。可选类型既可以是 `null`,也可以是已定义类型的值: @@ -366,7 +366,7 @@ while (rows.next()) |row| { } ``` -## [未定义的值 Undefined]($section.id('undefined')) +# [未定义的值 Undefined]($section.id('undefined')) 到目前为止,我们看到的每一个变量都被初始化为一个合理的值。但有时我们在声明变量时并不知道它的值。可选类型是一种选择,但并不总是合理的。在这种情况下,我们可以将变量设置为未定义,让其保持未初始化状态。 @@ -379,7 +379,7 @@ std.crypto.random.bytes(&pseudo_uuid); 上述代码仍然创建了一个 16 字节的数组,但它的每个元素都没有被赋值。 -## [错误 Errors]($section.id('errors')) +# [错误 Errors]($section.id('errors')) Zig 中错误处理功能十分简单、实用。这一切都从错误集(error sets)开始,错误集的使用方式类似于枚举: diff --git a/content/learn/06-pointers.smd b/content/learn/pointers.smd similarity index 98% rename from content/learn/06-pointers.smd rename to content/learn/pointers.smd index da7ef9c..3d1b7cd 100644 --- a/content/learn/06-pointers.smd +++ b/content/learn/pointers.smd @@ -1,6 +1,6 @@ --- .title = "指针", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2024-01-06T00:00:00"), .author = "Karl Seguin; ZigCC", .layout = "learn.shtml", .draft = false, @@ -191,7 +191,7 @@ pub const User = struct { 现在,代码已按预期运行。虽然在函数参数和内存模型方面仍有许多微妙之处,但我们正在取得进展。现在也许是一个好时机来说明一下,除了特定的语法之外,这些都不是 Zig 所独有的。我们在这里探索的模型是最常见的,有些语言可能只是向开发者隐藏了很多细节,因此也就隐藏了灵活性。 -## [方法]($section.id('fangfa')) +# [方法]($section.id('method')) 一般来说,我们会把 `levelUp` 写成 `User`结构的一个方法: @@ -210,7 +210,7 @@ pub const User = struct { 我最初选择函数是因为它很明确,因此更容易学习。 -## [常量函数参数]($section.id('const-func-param')) +# [常量函数参数]($section.id('const-func-param')) 我不止一次暗示过,在默认情况下,Zig 会传递一个值的副本(称为 "按值传递")。很快我们就会发现,实际情况要更微妙一些(提示:嵌套对象的复杂值怎么办?) @@ -222,7 +222,7 @@ pub const User = struct { > 也许你会想,即使与复制一个非常小的结构相比,通过引用传递怎么会更慢呢?我们接下来会更清楚地看到这一点,但要点是,当 `user` 是指针时,执行 `user.power` 会增加一点点开销。编译器必须权衡复制的代价和通过指针间接访问字段的代价。 -## [指向指针的指针]($section.id('pointer-to-pointer')) +# [指向指针的指针]($section.id('pointer-to-pointer')) 我们之前查看了`main`函数中 `user` 的内存结构。现在我们改变了 `levelUp`,那么它的内存会是什么样的呢? @@ -258,7 +258,7 @@ fn levelUp(user: *User) void { 我们可以使用多级间接指针,但这并不是我们现在所需要的。本节的目的是说明指针并不特殊,它只是一个值,即一个地址和一种类型。 -## [嵌套指针]($section.id('nested-pointer')) +# [嵌套指针]($section.id('nested-pointer')) 到目前为止,`User` 一直很简单,只包含两个整数。很容易就能想象出它的内存,而且当我们谈论『复制』 时,也不会有任何歧义。但是,如果 User 变得更加复杂并包含一个指针,会发生什么情况呢? @@ -371,7 +371,7 @@ pub const User = struct { 不同编程语言有不同的实现方式,但许多语言的工作方式与此完全相同(或非常接近)。虽然所有这些看似深奥,但却是日常编程的基础。好消息是,你可以通过简单的示例和片段来掌握这一点;它不会随着系统其他部分复杂性的增加而变得更加复杂。 -## [递归结构]($section.id('recursive-struct')) +# [递归结构]($section.id('recursive-structs')) 有时你需要一个递归结构。在保留现有代码的基础上,我们为 `User` 添加一个可选的 `manager` 字段,类型为 `?User`。同时,我们将创建两个`User`,并将其中一个指定为另一个的管理者: diff --git a/content/learn/01-preface.smd b/content/learn/preface.smd similarity index 100% rename from content/learn/01-preface.smd rename to content/learn/preface.smd diff --git a/content/learn/07-stack-memory.smd b/content/learn/stack-memory.smd similarity index 98% rename from content/learn/07-stack-memory.smd rename to content/learn/stack-memory.smd index 9b7db99..9b4cfde 100644 --- a/content/learn/07-stack-memory.smd +++ b/content/learn/stack-memory.smd @@ -1,6 +1,6 @@ --- .title = "栈内存", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2024-01-07T00:00:00"), .author = "Karl Seguin; ZigCC", .layout = "learn.shtml", .draft = false, @@ -18,7 +18,7 @@ > 三块内存区域实际上没有真正的物理差别。操作系统和可执行文件创造了"内存区域"这个概念。 -## [栈帧]($section.id('栈帧')) +# [栈帧]($section.id('stack-frame')) 迄今为止,我们所见的所有数据都是常量,存储在二进制的全局数据部分或作为局部变量。局部表示该变量只在其声明的范围内有效。在 Zig 中,范围从花括号开始到结束。大多数变量的范围限定在一个函数内,包括函数参数,或一个控制流块,比如 if。但是,正如所见,你可以创建任意块,从而创建任意范围。 @@ -92,7 +92,7 @@ main: user -> ------------- (id: 1043368d0) 与全局数据一样,调用栈也由操作系统和可执行文件管理。程序启动时,以及此后启动的每个线程,都会创建一个调用栈(其大小通常可在操作系统中配置)。调用栈在程序的整个生命周期中都存在,如果是线程,则在线程的整个生命周期中都存在。程序或线程退出时,调用栈将被释放。我们的全局数据包含所有程序的全局数据,而调用栈只包含当前执行的函数层次的栈帧。这样做既能有效利用内存,又能简化堆栈帧的管理。 -## [悬空指针]($section.id('悬空指针')) +# [悬空指针]($section.id('dangling-pointers')) 栈帧的简洁和高效令人惊叹。但它也很危险:当函数返回时,它的任何本地数据都将无法访问。这听起来似乎很合理,毕竟这是本地数据,但却会带来严重的问题。请看这段代码: diff --git a/content/learn/05-style-guide.smd b/content/learn/style-guide.smd similarity index 94% rename from content/learn/05-style-guide.smd rename to content/learn/style-guide.smd index 3a491de..bacce52 100644 --- a/content/learn/05-style-guide.smd +++ b/content/learn/style-guide.smd @@ -1,6 +1,6 @@ --- .title = "编码风格", -.date = @date("2024-01-01T00:00:00"), +.date = @date("2024-01-05T00:00:00"), .author = "Karl Seguin; ZigCC", .layout = "learn.shtml", .draft = false, @@ -10,7 +10,7 @@ 本小节的主要内容是介绍 Zig 编译器强制遵守的 2 条规则,以及 Zig 标准库的命名惯例(naming convention)。 -## [未使用变量 Unused Variable]($section.id('unused-variable')) +# [未使用变量 Unused Variable]($section.id('unused-variable')) Zig 编译器禁止`未使用变量`,例如以下代码会导致两处编译错误: @@ -55,7 +55,7 @@ fn add(a: i64, _: i64) i64 { 值得注意的是,在上述例子中,`std`也是一个未使用的符号,但是当前这种用法并不会导致任何编译错误。可能在未来,Zig 编译器也将此视为错误。 -## [变量覆盖 Variable Shadowing]($section.id('variable-shadowing')) +# [变量覆盖 Variable Shadowing]($section.id('variable-shadowing')) Zig 不允许使用同名的变量。下面是一个读取 `socket` 的例子,这个例子包含了一个变量覆盖的编译错误: @@ -74,7 +74,7 @@ fn read(stream: std.net.Stream) ![]const u8 { 我认为,这个规范并不能使代码可读性提高。在这个场景下,应该是开发者,而不是编译器,更有资格选择更有可读性的命名方案。 -## [命名规范]($section.id('naming-convention')) +# [命名规范]($section.id('naming-convention')) 除了遵守以上这些规则以外,开发者可以自由地选择他们喜欢的命名规范。但是,理解 Zig 自身的命名规范是有益的,因为大部分你需要打交道的代码,如 Zig 标准库,或者其他三方库,都采用了 Zig 的命名规范。 diff --git a/content/monthly/202207.smd b/content/monthly/202207.smd index 2cdd993..eaf2e29 100644 --- a/content/monthly/202207.smd +++ b/content/monthly/202207.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) - [Zig 初体验 - Keep Coding](https://liujiacai.net/blog/2022/07/16/zig-intro/) @@ -28,7 +28,7 @@ before starting with Zig? : Zig](https://www.reddit.com/r/Zig/comments/w63x6r/is_it_necessary_to_know_c_or_another_systems/) -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) - [Release bun v0.1.5 · oven-sh/bun](https://github.com/oven-sh/bun/releases/tag/bun-v0.1.5) @@ -47,7 +47,7 @@ - [How I built zig-sqlite](https://rischmann.fr/blog/how-i-built-zig-sqlite) -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-07-01..2022-08-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-07-01..2022-08-01) - [std 文档展示更全面](https://twitter.com/croloris/status/1550955321694330880) diff --git a/content/monthly/202208.smd b/content/monthly/202208.smd index d9ccb1c..841eaf3 100644 --- a/content/monthly/202208.smd +++ b/content/monthly/202208.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) - [Growing a {{mustache}} with Zig](https://zig.news/batiati/growing-a-mustache-with-zig-di4) @@ -29,7 +29,7 @@ devlog](https://devlog.hexops.com/2022/packed-structs-in-zig/)。使用 Zig 的 `packet struct` 实现 bit set 功能 -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) - [Virtual tables by vrischmann · Pull Request \#100 · vrischmann/zig-sqlite](https://github.com/vrischmann/zig-sqlite/pull/100)。zig-sqlite @@ -47,7 +47,7 @@ ![](/images/blockly.webp) -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-08-01..2022-09-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-08-01..2022-09-01) - [make self-hosted the default compiler by andrewrk · Pull Request \#12368](https://github.com/ziglang/zig/pull/12368) Master diff --git a/content/monthly/202209.smd b/content/monthly/202209.smd index 8a56507..1795eaf 100644 --- a/content/monthly/202209.smd +++ b/content/monthly/202209.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [Zig VS Rust 火花]($section.id('Zig VS Rust 火花')) +# [Zig VS Rust 火花]($section.id('Zig VS Rust 火花')) 在 9/10 号左右,在 Twitter 上牵起了一小波关于 Zig VS Rust 的小火花,以至于最后 Zig 创始人 Andrew Kelley @@ -16,7 +16,7 @@ Let us exist。这里稍微整理下这件事情的过程: 本次事件主要 - Rust 核心贡献者: Patrick Walton - Zig 社区 VP: Loris Cro -### [时间线]($section.id('时间线')) +## [时间线]($section.id('时间线')) - 8/26 号,一篇关于 wasm 2 Game Jam 的[分析报告](https://wasm4.org/blog/jam-2-results/)中,使用 Zig @@ -57,7 +57,7 @@ Let us exist。这里稍微整理下这件事情的过程: 本次事件主要 这里[提到的](https://github.com/tigerbeetledb/tigerbeetle/blob/main/docs/DESIGN.md)。而且即便内存安全,也可能发生 OOM -### [总结]($section.id('总结')) +## [总结]($section.id('总结')) 上面的链接比较多,这里稍微总结下这次争论的问题: @@ -69,7 +69,7 @@ Zig,笔者觉得大概率就是本次争论的观点,Rust 过于复杂,导致程序员不仅仅要考虑业务行为,还需要按照 Rust 的风格来编程,这加剧了程序出错的可能性。 -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) - [How (memory) safe is zig?](https://www.scattered-thoughts.net/writing/how-safe-is-zig/) @@ -102,7 +102,7 @@ Zig,笔者觉得大概率就是本次争论的观点,Rust - [Zig ⚡ Improving the User Experience for Unused Variables](https://vimeo.com/748218307) -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) - [Zig 开发常用类库](https://github.com/zigcc/forum/discussions/28),如果读者的 @@ -126,4 +126,4 @@ Zig,笔者觉得大概率就是本次争论的观点,Rust - [stm32f4.zig](https://github.com/moonxraccoon/stm32f4.zig) STM32F4(ARM Cortex M4 的高性能 32 位微控制器) 固件抽象层 -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?q=+is%3Aclosed+is%3Apr+closed%3A2022-09-01..2022-10-01+) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?q=+is%3Aclosed+is%3Apr+closed%3A2022-09-01..2022-10-01+) diff --git a/content/monthly/202210.smd b/content/monthly/202210.smd index b0f0673..925b42c 100644 --- a/content/monthly/202210.smd +++ b/content/monthly/202210.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) - [Zig Is Self-Hosted Now, What’s Next? | Loris Cro’s Blog](https://kristoff.it/blog/zig-self-hosted-now-what/) | 0.10 @@ -39,7 +39,7 @@ - \[音频\] [005. 与 LemonHX 畅聊新一代编程语言 Zig – RustTalk](https://rusttalk.github.io/podcast/005/) -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) - [Himujjal/zig-json5](https://github.com/Himujjal/zig-json5): A JSON5 parser/stringifier for Zig resembling the std.json API @@ -55,4 +55,4 @@ - [A minimal RocksDB example with Zig](https://notes.eatonphil.com/zigrocks.html) -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-10-01..2022-11-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-10-01..2022-11-01) diff --git a/content/monthly/202211.smd b/content/monthly/202211.smd index 54c342e..5b1435c 100644 --- a/content/monthly/202211.smd +++ b/content/monthly/202211.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [0.10.0 Release Notes](https://ziglang.org/download/0.10.0/release-notes.html) +# [0.10.0 Release Notes](https://ziglang.org/download/0.10.0/release-notes.html) 本月最大的事情就是 0.10 版本发布了,主要功能就是 self-hosted compiler,也称为『自举』,即可以用 Zig 来写 Zig @@ -17,13 +17,13 @@ compiler,也称为『自举』,即可以用 Zig 来写 Zig 赶紧升级吧,少年! -## [zigcc 中文社区微信群]($section.id('zigcc 中文社区微信群')) +# [zigcc 中文社区微信群]($section.id('zigcc 中文社区微信群')) 欢迎喜欢 Zig 的小伙伴加入! {{\< figure src=“https://github.com/zigcc/.github/raw/main/weixin.jpg” width=“200” title=“ZigCC 微信群二维码” \>}} -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) - [Wasmer 3.0 使用 Zig 进行跨平台编译 · Discussion \#35 · zigcc/forum](https://github.com/zigcc/forum/discussions/35) @@ -98,7 +98,7 @@ const Animal = union(enum){ [spoon](https://sr.ht/~leon_plickat/zig-spoon/) 这个库开发了一个帮助自己进行图片打标的 UI 工具, -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) - [Zig 程序设计语言中文手册](https://sxwangzhiwen.github.io/zigcndoc/zigcndoc.html) @@ -114,4 +114,4 @@ const Animal = union(enum){ - [Zig Support plugin for IntelliJ and CLion version 0.2.0 released](https://zig.news/marioariasc/zig-support-plugin-for-intellij-and-clion-version-020-released-3g06) -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-11-01..2022-12-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-11-01..2022-12-01) diff --git a/content/monthly/202212.smd b/content/monthly/202212.smd index 755bf47..e571d32 100644 --- a/content/monthly/202212.smd +++ b/content/monthly/202212.smd @@ -6,11 +6,11 @@ .draft = false, --- -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) - [Goodbye to the C++ Implementation of Zig ⚡ Zig Programming Language](https://ziglang.org/news/goodbye-cpp/) -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-12-01..2023-01-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2022-12-01..2023-01-01) diff --git a/content/monthly/202301.smd b/content/monthly/202301.smd index b4f0a1e..7fb1cf9 100644 --- a/content/monthly/202301.smd +++ b/content/monthly/202301.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [0.10.1](https://ziglang.org/download/0.10.1/release-notes.html) 版本发布 +# [0.10.1](https://ziglang.org/download/0.10.1/release-notes.html) 版本发布 一个小版本,主要是 bugfix。最主要的功能是:[Package Manager MVP](https://github.com/ziglang/zig/pull/14265),Zig @@ -59,7 +59,7 @@ pub fn build(b: *std.build.Builder) void { [15.0.7](http://releases.llvm.org/15.0.7/docs/ReleaseNotes.html) - 是 0.10.x 的最后一个 release 版本 -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) [Code study: interface idioms/patterns in zig standard libraries](https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj) @@ -77,7 +77,7 @@ Beetle](https://datastackshow.com/podcast/why-accounting-needs-its-own-database- Zig](https://0110.be/posts/Crossplatform_JNI_builds_with_Zig) 又一个使用 Zig 作为交叉编译的例子 -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) [Introducing ⚡zap⚡ - blazingly fast backends in zig](https://zig.news/renerocksai/introducing-zap-blazingly-fast-backends-in-zig-3jhh) @@ -98,4 +98,4 @@ Zig library for HyperLogLog estimation [This Week In Zig](https://thisweekinzig.mataroa.blog/) 一个介绍 Zig 的周刊,主要是 master 分支上的改动 -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-01-01..2023-02-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-01-01..2023-02-01) diff --git a/content/monthly/202302.smd b/content/monthly/202302.smd index 4afce6c..12527f1 100644 --- a/content/monthly/202302.smd +++ b/content/monthly/202302.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [包管理器进展]($section.id('包管理器进展')) +# [包管理器进展]($section.id('包管理器进展')) 包管理器自 [\#14265](https://github.com/ziglang/zig/pull/14265) 合并后一直在不断推进,以下两个是最主要的改变: @@ -61,7 +61,7 @@ 也欢迎大家给自己熟悉的 C/C++ 项目提 PR 让其支持 zig build,让构建不再那么痛苦。 -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) - [How a Zig IDE Could Work](https://matklad.github.io/2023/02/10/how-a-zig-ide-could-work.html) @@ -105,9 +105,9 @@ build,让构建不再那么痛苦。 - [Smoking Hot Binary Search In Zig](https://blog.deckc.hair/2023-02-22-smoking-hot-binary-search-in-zig.html) -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) - [Writing high-performance clients for TigerBeetle](https://tigerbeetle.com/blog/2023-02-21-writing-high-performance-clients-for-tigerbeetle/) -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-02-01..2023-03-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-02-01..2023-03-01) diff --git a/content/monthly/202303.smd b/content/monthly/202303.smd index 53a58d9..845840f 100644 --- a/content/monthly/202303.smd +++ b/content/monthly/202303.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) [Creating arbitrary error values by using error.Something syntax](https://www.reddit.com/r/zig/comments/11wmoky) @@ -61,7 +61,7 @@ Nix](https://flyx.org/cross-packaging/) [Zig And Rust](https://matklad.github.io/2023/03/26/zig-and-rust.html) -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) [macovedj/doink](https://github.com/macovedj/doink) Making WebAssembly Components with Zig @@ -80,7 +80,7 @@ Tiny desktop utility to keep notes [ringtailsoftware/zig-wasm-audio-framebuffer](https://github.com/ringtailsoftware/zig-wasm-audio-framebuffer) Examples of integrating Zig and Wasm for audio and graphics on the web -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-03-01..2023-04-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-03-01..2023-04-01) [zig build: run steps in parallel \#14647](https://github.com/ziglang/zig/pull/14647) @@ -98,7 +98,7 @@ bug,一个影响比较大的是 test 的构建方式不一样了。 现在 `addTest` 和 `addExecutable` 一样,输出都是 `CompileStep` ,它默认不会执行,需要调用 `run()` 拿到 run step 才可以。 -## [Zig 构建系统介绍]($section.id('Zig 构建系统介绍')) +# [Zig 构建系统介绍]($section.id('zig-build-system-intro')) 构建系统其实是 Zig 生态中比较重要的一环,和 Rust 不同,Zig 即是一门编程语言,也是一系列工具链,比如编译 Zig 时,没有 zigc diff --git a/content/monthly/202304.smd b/content/monthly/202304.smd index 9749e31..dee548b 100644 --- a/content/monthly/202304.smd +++ b/content/monthly/202304.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [重大事件]($section.id('重大事件')) +# [重大事件]($section.id('重大事件')) 在 2023 四月份的 [Tiobe](https://www.tiobe.com/tiobe-index/) 指数上,Zig [排名 @@ -29,7 +29,7 @@ Loris 发推表示这个数字对 Zig \> -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) [When should I use an UNTAGGED Union?](https://zig.news/kristoff/when-should-i-use-an-untagged-union-56ek) @@ -78,7 +78,7 @@ Systems Distributed 23 视频,[B 站链接](https://www.bilibili.com/video/BV1gP41117zY/),作者博客:[Scattered Thoughts](https://www.scattered-thoughts.net/) -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) [Coming Soon to a Zig Near You: HTTP Client](https://zig.news/nameless/coming-soon-to-a-zig-near-you-http-client-5b81) @@ -106,4 +106,4 @@ Zvisor is an open-source hypervisor written in the Zig programming language, which provides a modern and efficient approach to systems programming. -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) diff --git a/content/monthly/202305.smd b/content/monthly/202305.smd index 8a0919e..7654cd0 100644 --- a/content/monthly/202305.smd +++ b/content/monthly/202305.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [重大事件]($section.id('重大事件')) +# [重大事件]($section.id('重大事件')) 这个月主要的事情就是 HTTP server 在标准库的增加了,具体可参考: @@ -15,7 +15,7 @@ - [http server in the standard library · Issue \#910](https://github.com/ziglang/zig/issues/910) -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) [Integrating Zig and SwiftUI](https://mitchellh.com/writing/zig-and-swiftui) @@ -67,7 +67,7 @@ System](https://www.priver.dev/blog/zig/initial-commit-build-system/) [Writing DNS resolver in Zig](https://e-aakash.github.io/update/2023/06/04/resolv-dns-resolver-in-zig.html) -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) [Zig by Example](https://zigbyexample.github.io/) 非常好的学习资料。Learn How to use Zig’s Standard Library, by small @@ -100,4 +100,4 @@ A 2048 game to run in terminal [mitchellh/zig](https://github.com/mitchellh/zig-objc) Objective-C runtime bindings for Zig (Zig calling ObjC). -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-04-01..2023-05-01) diff --git a/content/monthly/202306.smd b/content/monthly/202306.smd index 4646b5c..cb8a897 100644 --- a/content/monthly/202306.smd +++ b/content/monthly/202306.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [重大事件]($section.id('重大事件')) +# [重大事件]($section.id('重大事件')) 一个是这个:[The Zig subreddit has closed](https://ziggit.dev/t/the-zig-subreddit-has-closed/679),现在 @@ -91,7 +91,7 @@ Zig 的各种 backend 进展,能不能给 fix 几个 regression?! 最后,即便 Zig 这个项目夭折了,我相信通过这个学习的过程也有助于提高我们对系统编程、编译器的理解,不是吗? -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) [A Note About Zig Books for the Zig Community](https://kristoff.it/blog/note-about-zig-books/) @@ -147,14 +147,14 @@ segfaults](https://www.openmymind.net/Zig-Danling-Pointers/) 讨论](https://news.ycombinator.com/item?id=36149462) -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) [pondzdev/duckdb](https://github.com/pondzdev/duckdb-proxy/) 一个将 DuckDB 数据库通过 HTTP API 暴露出来的代理,主要是利用了 [DuckDB C API](https://duckdb.org/docs/api/c/api.html),示例: ``` bash -## [open the database in readonly (DB must exist in this case)]($section.id('open the database in readonly (DB must exist in this case)')) +# [open the database in readonly (DB must exist in this case)]($section.id('open the database in readonly (DB must exist in this case)')) $ ./duckdb-proxy --readonly db/mydatabase.duckdb $ curl http://localhost:8012/api/1/exec \ @@ -184,7 +184,7 @@ Shell completions for the Zig compiler. [menduz/zig](https://github.com/menduz/zig-steamworks) Steamwork bindings for Zig. -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-05-01..2023-06-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-05-01..2023-06-01) - [WASI: Implement experimental threading support by Luukdegram · Pull Request \#16207 · diff --git a/content/monthly/202307.smd b/content/monthly/202307.smd index 7abedcb..cf8ac52 100644 --- a/content/monthly/202307.smd +++ b/content/monthly/202307.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [重大事件]($section.id('重大事件')) +# [重大事件]($section.id('重大事件')) Andrewk 在最新的文章 [The Upcoming Release Postponed Two More Weeks and Lacks Async Functions](https://ziglang.org/news/0.11.0-postponed-again/) @@ -24,7 +24,7 @@ Team](https://ziglang.org/news/welcome-jacob-young/),Core Team 恭喜 Core Team,又添一虎将! -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) [Copy Hunting | TigerBeetle](https://tigerbeetle.com/blog/2023-07-26-copy-hunting/) 比较有意思的文章,通过分析 LLVM 的 IR,来避免程序中不必要的 memcpy @@ -129,7 +129,7 @@ TigerBeetle 的新花样,把数据库搬到了 Web 上。[HN 比较有意思的文章,作者比较了 Clang、Zig 对相同逻辑代码生产的 LLVM IR,Zig 生成的 IR 更加精简 -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) [Zig helped us move data to the Edge. Here are our impressions](https://blog.turso.tech/zig-helped-us-move-data-to-the-edge-here-are-our-impressions-67d3a9c45af4) [Turso](https://turso.tech/) @@ -147,4 +147,4 @@ Experimental operating system written in Zig [EugenHotaj/ziggpt2](https://github.com/EugenHotaj/zig_gpt2) GPT-2 inference engine written in Zig -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-06-01..2023-07-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-06-01..2023-07-01) diff --git a/content/monthly/202308.smd b/content/monthly/202308.smd index 2a3c353..c8567ff 100644 --- a/content/monthly/202308.smd +++ b/content/monthly/202308.smd @@ -6,12 +6,12 @@ .draft = false, --- -## [0.11 正式发布](https://ziglang.org/download/0.11.0/release-notes.html#x86-Backend) +# [0.11 正式发布](https://ziglang.org/download/0.11.0/release-notes.html#x86-Backend) 0.11 终于在 8 月 4 号释出了,下面来看看它的一些重要改进吧。[HN 讨论](https://news.ycombinator.com/item?id=36995735) -## [Peer Type Resolution Improvements]($section.id('Peer Type Resolution Improvements')) +# [Peer Type Resolution Improvements]($section.id('Peer Type Resolution Improvements')) 对等类型解析算法得到改进,下面是一些在 0.10 中不能解析,但在 0.11 中可以解析的例子: @@ -29,7 +29,7 @@ 而且现在使用 `@intCast` 这类 builtin 都只接受一个参数,目前类型根据上下文自动推断出来。 -## [Multi-Object For Loops]($section.id('Multi-Object For Loops')) +# [Multi-Object For Loops]($section.id('Multi-Object For Loops')) 可以同时对多个对象进行遍历: @@ -45,7 +45,7 @@ for (input, 0..) |x, i| { } ``` -## [@min and @max]($section.id('@min and @max')) +# [@min and @max]($section.id('@min and @max')) 主要有两个改动: @@ -65,7 +65,7 @@ test "@min/@max refines result type" { } ``` -## [@inComptime]($section.id('@inComptime')) +# [@inComptime]($section.id('@inComptime')) 新加的 builtin,用于判断执行是否在 comptime 环境下执行: @@ -95,12 +95,12 @@ test "@inComptime" { } ``` -## [类型转化相关 builtin 的重命名]($section.id('类型转化相关 builtin 的重命名')) +# [类型转化相关 builtin 的重命名]($section.id('类型转化相关 builtin 的重命名')) 之前 `@xToY` 形式的 builtin 现在已经改成了 `@yFromX` ,这样的主要好处是便于阅读(从右向左),这是[草案](https://github.com/ziglang/zig/issues/6128)。 -## [Tuple 类型声明]($section.id('Tuple 类型声明')) +# [Tuple 类型声明]($section.id('Tuple 类型声明')) 现在可以直接用无 field 名字的 struct 来声明 tuple 类型: @@ -133,7 +133,7 @@ test "tuple declarations" { + const testcases = [_]struct { []const u8, []const u8, bool }{ ``` -## [排序]($section.id('排序')) +# [排序]($section.id('排序')) 现在排序算法分布两类: @@ -143,7 +143,7 @@ test "tuple declarations" { 与堆排的快速最差情况相结合,同时在具有特定模式的输入上达到线性时间。 -## [Stack Unwinding]($section.id('Stack Unwinding')) +# [Stack Unwinding]($section.id('Stack Unwinding')) Zig 之前依赖 [frame pointer](https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers) @@ -154,20 +154,20 @@ pointer](https://en.wikipedia.org/wiki/Call_stack#Stack_and_frame_pointers) DWARF unwind tables 和 MachO compact unwind information 来会恢复堆栈,详见:[\#15823](https://github.com/ziglang/zig/pull/15823)。 -## [包管理]($section.id('包管理')) +# [包管理]($section.id('包管理')) 0.11 首次正式引入了包管理器,具体解释可以参考:[Zig Build System](https://en.liujiacai.net/2023/04/13/zig-build-system/),而且很重要一点,step 之间可以[并发执行](https://ziglang.org/download/0.11.0/release-notes.html#Steps-Run-In-Parallel), -## [Bootstrapping]($section.id('Bootstrapping')) +# [Bootstrapping]($section.id('Bootstrapping')) C++ 实现的 Zig 编译器已经被彻底移除,这意味着 `-fstage1` 不再生效,Zig 现在只需要一个 2.4M 的 WebAssembly 文件和一个 C 编辑器即可,工作细节可以参考:[Goodbye to the C++ Implementation of Zig](https://ziglang.org/news/goodbye-cpp/)。 -## [代码生成]($section.id('代码生成')) +# [代码生成]($section.id('代码生成')) 虽然 Zig 编译器现在还是主要使用 LLVM 来进行代码生成,但在这次发布中,其他几个后端也有了非常大的进步: @@ -182,7 +182,7 @@ Zig](https://ziglang.org/news/goodbye-cpp/)。 后端专注于为 OpenCL 内核生成代码,不过未来可能也会支持兼容 Vulkan 的着色器。 -## [增量编译]($section.id('增量编译')) +# [增量编译]($section.id('增量编译')) 虽然这仍是一个高度 WIP 的功能,但这一版本周期中的许多改进为编译器的增量编译功能铺平了道路。其中最重要的是 @@ -190,7 +190,7 @@ Zig](https://ziglang.org/news/goodbye-cpp/)。 用户大多看不到这一改动,但它为编译器带来了许多好处,其中之一就是我们现在更接近增量编译了。增量编译将是 0.12.0 发布周期的重点。 -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) [Error Handling In Zig](https://www.aolium.com/karlseguin/4013ac14-2457-479b-e59b-e603c04673c8) 又一篇讨论错误处理的文章 @@ -222,9 +222,9 @@ Andrew 的最新文章,远离社交平台! [Taking off with Zig: Putting the Z in Benchmark — Double Trouble](https://double-trouble.dev/post/zbench/) -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) - [Mach v0.2 released](https://devlog.hexops.com/2023/mach-v0.2-released/) -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-07-01..2023-08-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-07-01..2023-08-01) diff --git a/content/monthly/202309.smd b/content/monthly/202309.smd index 5fea949..52332bb 100644 --- a/content/monthly/202309.smd +++ b/content/monthly/202309.smd @@ -6,9 +6,9 @@ .draft = false, --- -## [重大事件]($section.id('重大事件')) +# [重大事件]($section.id('重大事件')) -## [Bounties Damage Open Source Projects](https://ziglang.org/news/bounties-damage-open-source-projects/) +# [Bounties Damage Open Source Projects]($section.id('bounties-damage-open-source-projects')) 在 2023-09-11 号,Wasmerio CEO 创建了 [Support WASIX · Issue \#17115](https://github.com/ziglang/zig/issues/17115),表示想赞助 Zig @@ -23,7 +23,7 @@ Andrew 与 Loris 在这篇文章中主要阐述了这么做为什么是伤害社 社区,他更想保证 Zig 的独立性,这也是他们创办 [Software You Can Love](https://kristoff.it/blog/the-open-source-game/) 的初衷。 -## [Bun 1.0](https://bun.sh/blog/bun-v1.0) +# [Bun 1.0](https://bun.sh/blog/bun-v1.0) 面包终于出炉了!Bun 毫无疑问是 Zig 的明星项目,它在 2023-09-08 正式发布了 1.0 版本,这对开发者来说可能没什么太大的区别,毕竟就只是个 @@ -38,7 +38,7 @@ Bun 并不简单的是个 Node.js 替代品,而是大而全的工具链: - Testing libraries,内置 test runner,支持快照测试、模拟和代码覆盖,可以替代:jest、ts-test -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('观点/教程')) [Kiesel Devlog \#1: Now passing 25% of test262](https://linus.dev/posts/kiesel-devlog-1/) 另一个 devlog,作者写了一个 JS engine 用来学习 Zig,该作者是 SerenityOS @@ -85,7 +85,7 @@ Mitchell Hashimoto 在这篇文章里分享了开发终端 Ghostty 时用的 Zig [SoA 内存布局]($image.siteAsset('images/soa-layout.webp')) -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('项目/工具')) [ZigBrains](https://plugins.jetbrains.com/plugin/22456-zigbrains) A multifunctional Zig Programming Language plugin for the IDEA platform. @@ -114,7 +114,7 @@ Reactive signal/Dependency tracking library in Zig. [buzz-language/buzz](https://github.com/buzz-language/buzz) A small/lightweight statically typed scripting language -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-08-01..2023-09-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-08-01..2023-09-01) [Aro translate](https://github.com/ziglang/zig/pull/17221) 用 Zig 写的 [Aro](https://github.com/Vexu/arocc) 来替换 clang,来实现 diff --git a/content/monthly/202310.smd b/content/monthly/202310.smd index 036b50c..b9b30af 100644 --- a/content/monthly/202310.smd +++ b/content/monthly/202310.smd @@ -6,16 +6,16 @@ .draft = false, --- -## [重大事件]($section.id('重大事件')) +# [重大事件]($section.id('major-events')) -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('opinion-tutorial')) [Notes From the Field: Learning Zig](https://registerspill.thorstenball.com/p/notes-from-the-field-learning-zig) Zig 初学者的使用经验分享 [Friendly Neighbor: A network service for Linux wake-on-demand, written in Zig](https://dgross.ca/blog/friendly-neighbor-announce/) 作者在这篇文章中分享了 用 Zig 重写之前 Ruby -写的一个网络工具,一方面是减轻资源消耗,另一方面是探索用“低级”语言来写程序。不错的案例分享。 +写的一个网络工具,一方面是减轻资源消耗,另一方面是探索用"低级"语言来写程序。不错的案例分享。 [Zig Interfaces](https://www.openmymind.net/Zig-Interfaces/) 作者介绍了 Zig @@ -96,7 +96,7 @@ pub fn main() !void { 由于 Zig 还在快速开发迭代中,因此项目很有可能出现新版本 Zig 无法编译的情况,这篇文章介绍了一些管理多个 Zig 版本的方式。 -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('projects-tools')) [zigcli](https://zigcli.liujiacai.net/) a toolkit for building command lines programs in Zig @@ -115,4 +115,4 @@ Enable the use of Zig code in JavaScript project Language server for GLSL (autocomplete, goto-definition, formatter, and more) -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-09-01..2023-10-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-09-01..2023-10-01) diff --git a/content/monthly/202311.smd b/content/monthly/202311.smd index 94ad810..78d02ff 100644 --- a/content/monthly/202311.smd +++ b/content/monthly/202311.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [重大事件]($section.id('重大事件')) +# [重大事件]($section.id('major-events')) 本月讨论比较多的就是 [Zig May Pass Anything By Reference](https://www.1a-insec.net/blog/25-zig-reference-semantics/) @@ -58,7 +58,7 @@ precisely』,目前来看确实是这样的,而且 core team - [Design flaw: Swapping struct fields yields unexpected value \#12064](https://github.com/ziglang/zig/issues/12064) -## [观点/教程]($section.id('观点/教程')) +# [观点/教程]($section.id('opinion-tutorial')) [Zig's std.json.Parsed(T)](https://www.openmymind.net/Zigs-std-json-Parsed/) 老朋友 openmymind 的文章,这篇文章主要讲述了使用 Zig 中的 json @@ -147,7 +147,7 @@ Fields - 一个包的兼容性,应该由文档来解释 - 增加私有字段,会增加语言的复杂度,而且这种复杂性本身是完全可以避免的 -## [项目/工具]($section.id('项目/工具')) +# [项目/工具]($section.id('projects-tools')) [zig build explained – building C/C++ projects](https://zig.news/xq/zig-build-explained-part-2-1850) 经典文章回顾,如何使用 Zig 构建系统编译 C/C++ 项目 @@ -155,4 +155,4 @@ Fields [akhildevelops/cudaz](https://github.com/akhildevelops/cudaz) A Zig Cuda wrapper -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-10-01..2023-11-01) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2023-10-01..2023-11-01) diff --git a/content/monthly/202402.smd b/content/monthly/202402.smd index 88e5aeb..2e05929 100644 --- a/content/monthly/202402.smd +++ b/content/monthly/202402.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [重大事件 ($section.id('major-events'))]($section.id('重大事件 ($section.id('major-events'))')) +# [重大事件]($section.id('major-events')) Andrew 最近在 zigshow 节目中介绍了 Zig 2024 年的规划,主要有以下几点: @@ -32,7 +32,7 @@ Andrew 最近在 zigshow 节目中介绍了 Zig 2024 年的规划,主要有以 \#91](https://github.com/orgs/zigcc/discussions/91) B 站搬运地址:https://www.bilibili.com/video/BV1UC4y167w9/ -## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) +# [观点/教程]($section.id('opinion-tutorial')) [Fast-growing Zig tops Stack Overflow survey for highest-paid programming language](https://www.infoworld.com/article/3713082/fast-growing-zig-tops-stack-overflow-survey-for-highest-paid-programming-language.html) 估计是 Bun 带动的贫富差距?! @@ -67,7 +67,7 @@ Syndica 公司的 Sig,这是一款用 Zig 编写的、专注于 RPS 的 Solana - [Senior Software Engineer (Zig/C/Rust) @ Syndica](https://jobs.ashbyhq.com/syndica/15ab4e32-0f32-41a0-b8b0-16b6518158e9) -## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) +# [项目/工具]($section.id('projects-tools')) [semickolon/kirei](https://github.com/semickolon/kirei) 🌸 The prettiest keyboard software @@ -94,4 +94,4 @@ Zig-TypeScript binding generator 🟦 🦎 [sneekyfoxx/ziggy](https://github.com/sneekyfoxx/ziggy) 又又一个 Zig 版本管理工具 -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2023-03-01) ($section.id('zig-updates-202402')) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2023-03-01) diff --git a/content/monthly/202403.smd b/content/monthly/202403.smd index 531d570..0114028 100644 --- a/content/monthly/202403.smd +++ b/content/monthly/202403.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [重大事件 ($section.id('major-events'))]($section.id('重大事件 ($section.id('major-events'))')) +# [重大事件]($section.id('major-events')) > @@ -39,7 +39,7 @@ Kelley](https://rustacean-station.org/episode/andrew-kelley/) 也会紧密关注发布动态,有消息第一时间分享给大家。耐不住寂寞的朋友,可以先去刷刷 Zig 的 discord。 -## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) +# [观点/教程]($section.id('opinion-tutorial')) [Redesign How Autodoc Works](https://github.com/ziglang/zig/pull/19208) Andrew 在这个 PR 里重构了现有的文档系统 @@ -235,7 +235,7 @@ const port = port: { [Using Zig with WebAssembly](https://blog.mjgrzymek.com/blog/zigwasm) 如何将 Zig 编译成 wasm,并传递复杂的参数。 -## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) +# [项目/工具]($section.id('projects-tools')) [xataio/pgzx](https://github.com/xataio/pgzx) Create PostgreSQL extensions using Zig. 一个例子: @@ -291,4 +291,4 @@ ability of running pipeline like bash. [zigcc/zig-milestone](https://github.com/zigcc/zig-milestone) Zig milstone monitor -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2024-03-01) ($section.id('zig-updates-202403')) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-02-01..2024-03-01) diff --git a/content/monthly/202404.smd b/content/monthly/202404.smd index 3b88fdf..098f207 100644 --- a/content/monthly/202404.smd +++ b/content/monthly/202404.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [重大事件 ($section.id('major-events'))]($section.id('重大事件 ($section.id('major-events'))')) +# [重大事件]($section.id('major-events')) 千呼万唤的 0.12.0 版本终于 2024-04-20 正式释出了!这次版本历时 8 个月,有 268 位贡献者,一共进行了 3688 次提交!社区内的一些讨论:[Hacker @@ -37,7 +37,7 @@ CMake、Makefile、Vcpkg、Git submodule 等工具,所有的依赖使用 zon 期待一年后 Zig 的生态! -## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) +# [观点/教程]($section.id('opinion-tutorial')) [Zig 中任意精度整数用途与实现](https://github.com/zigcc/forum/issues/112) 由于 CPU @@ -104,7 +104,7 @@ Vector,到最后利用 bit 的特点,来逐步优化,并用 godbolt [Documentation takes another step backwards : r/Zig](https://www.reddit.com/r/Zig/comments/1cc1x2v/documentation_takes_another_step_backwards/) 一个 Reddit 用户对文档的抱怨 -## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) +# [项目/工具]($section.id('projects-tools')) [rofrol/zig-companies](https://github.com/rofrol/zig-companies) A list of companies using Zig in production. @@ -124,4 +124,4 @@ Zig string library that includes small string optimization on the stack [FalsePattern/ZigBrains](https://github.com/FalsePattern/ZigBrains) Yet another zig language plugin for intellij -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-04-01..2024-05-01) ($section.id('zig-updates-202404')) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-04-01..2024-05-01) diff --git a/content/monthly/202405.smd b/content/monthly/202405.smd index d62d74b..a64929a 100644 --- a/content/monthly/202405.smd +++ b/content/monthly/202405.smd @@ -6,14 +6,14 @@ .draft = false, --- -## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) +# [观点/教程]($section.id('opinion-tutorial')) -## [Thoughts on Zig](https://arne.me/blog/thoughts-on-zig) ($section.id('thoughts-on-zig')) +# [Thoughts on Zig](https://arne.me/blog/thoughts-on-zig) 又一篇 Zig 初学者的使用体验文档,如果你也在犹豫要不要学 Zig,这是个不错的经验参考。 -## [I'm sold on Zig's simplicity : r/Zig](https://www.reddit.com/r/Zig/comments/1ckstjv/im_sold_on_zigs_simplicity/) ($section.id('zig-simplicity')) +# [I'm sold on Zig's simplicity : r/Zig]($section.id('im-sold-on-zigs-simplicity-r-zig')) 一个具有资深经验开发者,在这里描述了自己选择业余项目语言的经历: @@ -32,7 +32,7 @@ Zig,这是个不错的经验参考。 - 唯一缺失的就是『接口』,但这一点并不是很关键,就像在 C里也没有,但是 C 可以做任何事 -## [Zig's New CLI Progress Bar Explained](https://andrewkelley.me/post/zig-new-cli-progress-bar-explained.html) ($section.id('zig-cli-progress-bar')) +# [Zig's New CLI Progress Bar Explained]($section.id('zigs-new-cli-progress-bar-explained')) Andrew 的一篇文章,讲述了在最新版的 Zig 中,对进度条的改进实现,现在的进度展示更加友好。 @@ -42,7 +42,7 @@ Andrew 的一篇文章,讲述了在最新版的 Zig - 首先通过预先分配好需要使用的结构,保证后续无需在进行 heap 申请 - 通过 atomic 操作来实现一个无锁的 freelist,用于申请、释放 Node -## [Writing a task scheduler in Zig](https://www.openmymind.net/Writing-a-Task-Scheduler-in-Zig/) ($section.id('task-scheduler-zig')) +# [Writing a task scheduler in Zig](https://www.openmymind.net/Writing-a-Task-Scheduler-in-Zig/) Openmymind 作者的又一力作,通过编写一个任务调度器,讲述了多线程编程的基本要领: @@ -81,7 +81,7 @@ Openmymind } ``` -## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) +# [项目/工具]($section.id('projects-tools')) [zigar](https://github.com/chung-leong/zigar) Enable the use of Zig code in JavaScript project。它可以让你直接在 JS @@ -96,4 +96,4 @@ A generic and general purpose Set implementation for the Zig language [akarpovskii/tuile](https://github.com/akarpovskii/tuile) A Text UI library for Zig -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-05-01..2024-06-01) ($section.id('zig-updates-202405')) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-05-01..2024-06-01) diff --git a/content/monthly/202406.smd b/content/monthly/202406.smd index d2966f2..373b107 100644 --- a/content/monthly/202406.smd +++ b/content/monthly/202406.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [重大事件 ($section.id('major-events'))]($section.id('重大事件 ($section.id('major-events'))')) +# [重大事件]($section.id('major-events')) 2024-06-07,0.13.0 发布,历时不足 2 个月,有 73 位贡献者,一共进行了 415 次提交! 这是一个相对较短的发布周期,主要原因是工具链升级,例如升级到 @@ -29,9 +29,9 @@ const map = std.StaticStringMap(T).initComptime(kvs_list); - 启用增量编译以实现快速重建。 - 将并发引入语义分析,进一步提高编译速度。 -## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) +# [观点/教程]($section.id('opinion-tutorial')) -## [Leveraging Zig's Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/) ($section.id('zig-allocators')) +# [Leveraging Zig's Allocators](https://www.openmymind.net/Leveraging-Zigs-Allocators/) 老朋友 openmymind 的又一篇好文章:如何利用 Zig 的 Allocator 来实现请求级别的内存分配。 Zig Allocator @@ -99,7 +99,7 @@ fn run(worker: *Worker) void { } } -## [On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/) ($section.id('zig-vs-rust-at-work')) +# [On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/) 这篇文章作者描述了所在公司在改造老 C/C++ 项目时,为什么选择了 Zig 而不是 Rust。 重写的项目运行在多个平台上(Web、移动端、VR @@ -128,14 +128,14 @@ Rust。 重写的项目运行在多个平台上(Web、移动端、VR 相信这也是大部分人选择 Zig 的原因:简洁、高效。 -## [Packing some Zig before going for the countryside](https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/) ($section.id('packing-zig-countryside')) +# [Packing some Zig before going for the countryside](https://ludwigabap.bearblog.dev/packing-some-zig-before-going-for-the-countryside/) 作者列举的一些 Zig 学习资料、常用类库。该作者的另一篇文章也有不少资料:[2024 Collection of Zig resources](https://ludwigabap.bearblog.dev/2024-collection-of-zig-resources/) -## [Why I am not yet ready to switch to Zig from Rust](https://turso.tech/blog/why-i-am-not-yet-ready-to-switch-to-zig-from-rust) ($section.id('not-switching-to-zig')) +# [Why I am not yet ready to switch to Zig from Rust]($section.id('why-i-am-not-yet-ready-to-switch-to-zig-from-rust')) Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较喜欢 C,但 C 不是一门安全的语言,因此通过 Rust,作者可以避免 写出 SIGSEGVS @@ -156,7 +156,7 @@ Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较 其他社区的一些讨论:[Lobsters](https://lobste.rs/s/0mnhdx)、[Hacker News](https://news.ycombinator.com/item?id=40681862) -## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) +# [项目/工具]($section.id('projects-tools')) [malcolmstill/zware](https://github.com/malcolmstill/zware) Zig WebAssembly Runtime Engine @@ -165,4 +165,4 @@ Zig WebAssembly Runtime Engine iouring like asynchronous API and coroutine powered IO tasks for zig -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-06-01..2024-07-01) ($section.id('zig-updates-202406')) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-06-01..2024-07-01) diff --git a/content/monthly/202407.smd b/content/monthly/202407.smd index b8a6bcb..63234c2 100644 --- a/content/monthly/202407.smd +++ b/content/monthly/202407.smd @@ -6,7 +6,7 @@ .draft = false, --- -## [重大事件 ($section.id('major-events'))]($section.id('重大事件 ($section.id('major-events'))')) +# [重大事件]($section.id('major-events')) 在[这篇文章](https://leaddev.com/tech/why-zig-one-hottest-programming-languages-learn)里,作者引用 Stackoverflow 2024 @@ -34,9 +34,9 @@ Zig 开发者的一些观点: 但与此同时,这些语言的工具链却非常糟糕。 Zig 允许用户涉猎这些核心编程语言,但可以使用更好的工具链,兼容各种语言和更丰富的功能。 -## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) +# [观点/教程]($section.id('opinion-tutorial')) -## [Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/) ($section.id('improving-zls-experience')) +# [Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/) Loris Cro 的最新文章,介绍了一个改进 Zig 编码体验的小技巧,十分推荐大家使用。具体来说是这样的: 通过配置 @@ -82,7 +82,7 @@ zls --config-path zls.json 这样不同的项目就可以用不同的检查步骤了。 -## [Systems Distributed '24](https://guergabo.substack.com/p/systems-distributed-24) ($section.id('systems-distributed-24')) +# [Systems Distributed '24]($section.id('systems-distributed-24')) 作者对这次会议的一个回顾总结,议题主要有如下几个方向: @@ -92,7 +92,7 @@ zls --config-path zls.json - Lessons from Building Distributed Databases - Notes from Water Cooler Chats -## [C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself](https://jstrieb.github.io/posts/c-reflection-zig/) ($section.id('c-macro-reflection-zig')) +# [C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself](https://jstrieb.github.io/posts/c-reflection-zig/) 该作者分享了利用 typeInfo 来在编译时获取字段名的能力,要知道,在 C 里面是没有这个功能的。 @@ -135,7 +135,7 @@ fn get_window_messages() [65536][:0]const u8 { } ``` -## [A TypeScripter's Take on Zig (Advent of Code 2023)](https://effectivetypescript.com/2024/07/17/advent2023-zig/) ($section.id('typescript-take-advent2023-zig')) +# [A TypeScripter's Take on Zig (Advent of Code 2023)](https://effectivetypescript.com/2024/07/17/advent2023-zig/) 以下该作者的一些心得体会: @@ -167,7 +167,7 @@ fn get_window_messages() [65536][:0]const u8 { - 在 JavaScript 允许您内联表达式的某些情况下,您可能需要分解出一个变量来澄清生存期。看看[这个问题](https://github.com/ziglang/zig/issues/12414)。 -## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) +# [项目/工具]($section.id('projects-tools')) [18alantom/fex](https://github.com/18alantom/fex) A command-line file explorer prioritizing quick navigation. @@ -175,4 +175,4 @@ A command-line file explorer prioritizing quick navigation. [griush/zm](https://github.com/griush/zm) SIMD Math library fully cross-platform -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-07-01..2024-08-01) ($section.id('zig-updates-202407')) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-07-01..2024-08-01) diff --git a/content/monthly/202410.smd b/content/monthly/202410.smd index be7a141..d0b4b3b 100644 --- a/content/monthly/202410.smd +++ b/content/monthly/202410.smd @@ -6,9 +6,9 @@ .draft = false, --- -## [重大事件 ($section.id('major-events'))]($section.id('重大事件 ($section.id('major-events'))')) +# [重大事件]($section.id('major-events')) -## [向 Zig 软件基金会认捐 30 万美元 ($section.id('donation-zsf-300k'))]($section.id('向 Zig 软件基金会认捐 30 万美元 ($section.id('donation-zsf-300k'))')) +# [向 Zig 软件基金会认捐 30 万美元]($section.id('donation-zsf-300k')) Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金会 (ZSF) 捐赠了 300,000 美元。 @@ -25,9 +25,9 @@ Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金 作为其中的一部分,我们希望支持那些我们认为可以带来变革和影响的独立软件项目,这既是回馈给我如此之多的社区的一种方式,更重要的是,这也是彰显和鼓励为热爱而构建的文化的一种方式。 Zig 就是这样一个项目。 -## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) +# [观点/教程]($section.id('opinion-tutorial')) -## [Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/) ($section.id('zig-vs-c')) +# [Zig is everything I want C to be](https://mrcat.au/blog/zig_is_cool/) 对 Zig 的特色进行了简单扼要的介绍,主要有: @@ -61,9 +61,9 @@ Zig 就是这样一个项目。 4. 与 C 无缝交互, `zig cc` 是交叉编译的首选 -## [Building Nintendo 3DS Homebrew with Zig](https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html) ($section.id('3ds-homebrew-zig')) +# [Building Nintendo 3DS Homebrew with Zig](https://blog.erikwastaken.dev/posts/2024-10-27-building-3ds-homebrew-with-zig.html) -## [Critical Social Infrastructure for Zig Communities | Loris Cro's Blog](https://kristoff.it/blog/critical-social-infrastructure/) ($section.id('zig-community-infrastructure')) +# [Critical Social Infrastructure for Zig Communities | Loris Cro's Blog](https://kristoff.it/blog/critical-social-infrastructure/) 对于一个试图共同学习如何制作大家都喜欢的软件的社区来说,能够分享想法并开展合作至关重要,但社交平台的不断起伏会导致连接中断,这对于一个从一开始就希望去中心化的社区来说是个大问题。 @@ -74,25 +74,23 @@ Zig 就是这样一个项目。 - - -## [The Zig Website Has Been Re-engineered](https://ziglang.org/news/website-zine/) ($section.id('zig-website-reengineered')) +# [The Zig Website Has Been Re-engineered]($section.id('the-zig-website-has-been-re-engineered')) Zig 官网已经用 [Zine](https://zine-ssg.io/) 重写! -## [Rust vs. Zig in Reality: A (Somewhat) Friendly Debate](https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/) ($section.id('rust-vs-zig-debate')) +# [Rust vs. Zig in Reality: A (Somewhat) Friendly Debate](https://thenewstack.io/rust-vs-zig-in-reality-a-somewhat-friendly-debate/) -## [Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun](https://kevinlynagh.com/rust-zig/) ($section.id('rust-keyboard-firmware-zig')) +# [Why I rewrote my Rust keyboard firmware in Zig: consistency, mastery, and fun]($section.id('why-i-rewrote-my-rust-keyboard-firmware-in-zig-consistency-mastery-and-fun')) -## [Why Zig Programmers Are Cashing In: A Deep Dive into the Lucrative World of Zig](https://teckrevie.blogspot.com/2024/09/why-zig-programmers-are-cashing-in-deep.html) ($section.id('zig-programmers-cashing-in')) +# [视频]($section.id('videos')) -## [视频 ($section.id('videos'))]($section.id('视频 ($section.id('videos'))')) +## [I made an operating system that self replicates doom on a network "from scratch"](https://www.youtube.com/watch?v=xOySJpQlmv4&feature=youtu.be) -### [I made an operating system that self replicates doom on a network "from scratch"](https://www.youtube.com/watch?v=xOySJpQlmv4&feature=youtu.be) ($section.id('os-self-replicates-doom')) +## [Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature=shared&v=3fWx5BOiUiY) -### [Rust vs Zig vs Go: Performance (Latency - Throughput - Saturation - Availability)](https://www.youtube.com/watch?feature=shared&v=3fWx5BOiUiY) ($section.id('rust-zig-go-performance')) +## [Let's explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764) -### [Let's explore Vulkan API with Zig programming language from scratch](https://www.youtube.com/live/Kf7BIPUUfsc?t=764) ($section.id('vulkan-api-zig')) - -## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) +# [项目/工具]($section.id('projects-tools')) [laohanlinux/boltdb-zig](https://github.com/laohanlinux/boltdb-zig) a zig implement kv database @@ -131,4 +129,4 @@ Video editor 🎬 written in Zig ⚡ using raylib [pwbh/ymlz](https://github.com/pwbh/ymlz) Small and convenient yaml parser for Zig -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-10-01..2024-11-01) ($section.id('zig-updates-202410')) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-10-01..2024-11-01) diff --git a/content/monthly/202411.smd b/content/monthly/202411.smd index eb8e410..55081fc 100644 --- a/content/monthly/202411.smd +++ b/content/monthly/202411.smd @@ -6,9 +6,9 @@ .draft = false, --- -## [观点/教程 ($section.id('opinion-tutorial'))]($section.id('观点/教程 ($section.id('opinion-tutorial'))')) +# [观点/教程]($section.id('opinion-tutorial')) -## [Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html) ($section.id('js-toolchain-in-zig')) +# [Why am I writing a JavaScript toolchain in Zig?](https://injuly.in/blog/announcing-jam/index.html) [JAM](https://github.com/srijan-paul/jam) 作者写的一篇文章,分析里市面上现有的 JS @@ -48,7 +48,7 @@ esquery 的问题在于执行效率,通过借助于 zig 的 comptime 来在编译器翻译 esquery 就避免了运行时开销。 -## [Advent of Code in Zig | Loris Cro's Blog](https://kristoff.it/blog/advent-of-code-zig/) ($section.id('advent-of-code-zig')) +# [Advent of Code in Zig | Loris Cro's Blog]($section.id('advent-of-code-in-zig-loris-cros-blog')) 一年一度的 AoC 又到了,这篇文章里给出了一些使用的技巧来帮助大家用 Zig 来解决 AoC 问题。 @@ -99,9 +99,9 @@ > 的某些功能甚至能帮助您取得比其他语言更快的进展,但它最终还是针对软件工程进行了优化,而这并不是您在 > AoC 中要做的事情。 -## [Zig and Emulators](https://floooh.github.io/2024/08/24/zig-and-emulators.html) ($section.id('zig-and-emulators')) +# [Zig and Emulators]($section.id('zig-and-emulators')) -## [Zig Reproduced Without Binaries](https://jakstys.lt/2024/zig-reproduced-without-binaries/) ($section.id('zig-reproduced-without-binaries')) +# [Zig Reproduced Without Binaries](https://jakstys.lt/2024/zig-reproduced-without-binaries/) 一个很有趣的实验,在 0.10 版本中,Zig 编译器实现了自举,即可以用老版本的 Zig 来编译 Zig 源码,生成最新的 Zig @@ -130,7 +130,7 @@ Zig,利用 LLVM 后端,以 wasm32-wasi 为目标生成的二进制文件。 - [Why is the bootstrapping process so complicated? : r/Zig](https://www.reddit.com/r/Zig/comments/142gwls/why_is_the_bootstrapping_process_so_complicated/) -## [项目/工具 ($section.id('projects-tools'))]($section.id('项目/工具 ($section.id('projects-tools'))')) +# [项目/工具]($section.id('projects-tools')) [Builds (Zig) - GoReleaser](https://goreleaser.com/customization/zig-builds/) 版本发布工具 GoReleaser 支持了 Zig @@ -151,4 +151,4 @@ PDF reader for terminal emulators using the Kitty image protocol [Dr-Nekoma/lyceum](https://github.com/Dr-Nekoma/lyceum) An MMO game written in Erlang (+ PostgreSQL) + Zig (+ Raylib) -## [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-11-01..2024-12-01) ($section.id('zig-updates-202411')) +# [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-11-01..2024-12-01) diff --git a/content/post/0.14.smd b/content/post/0.14.smd index f38b239..de8aa00 100644 --- a/content/post/0.14.smd +++ b/content/post/0.14.smd @@ -8,11 +8,11 @@ https://ziglang.org/download/0.14.0/release-notes.html -## [发布概览]($section.id('release-overview')) +# [发布概览]($section.id('release-overview')) Zig 0.14.0 版本是经过 **9 个月的工作**,由 **251 位不同的贡献者** 完成,包含 **3467 个提交** 的成果。该版本专注于提升 **健壮性**、**最优性** 和 **可重用性**,并通过 Zig 软件基金会 (Zig Software Foundation) 资助开发。 -## [核心主题与重要更新]($section.id('key-topics-and-updates')) +# [核心主题与重要更新]($section.id('key-topics-and-updates')) 1. **提升编译速度与开发效率:** @@ -124,13 +124,13 @@ Zig 0.14.0 版本是经过 **9 个月的工作**,由 **251 位不同的贡献 - 最终目标是达到 1.0.0 版本,届时 Tier 1 支持将包含 bug 政策。 -## [关于 Zig 0.14.0 版本的常见问题解答]($section.id('faq-zig-0-14-0')) +# [关于 Zig 0.14.0 版本的常见问题解答]($section.id('faq-zig-0-14-0')) -## [Zig 0.14.0 版本的主要更新和亮点是什么?]($section.id('main-updates-and-highlights')) +# [Zig 0.14.0 版本的主要更新和亮点是什么?]($section.id('main-updates-and-highlights')) Zig 0.14.0 版本是长达 9 个月开发工作和 3467 次提交的成果,主要亮点包括:显著增强了对多种目标平台的支持,包括 arm/thumb、mips/mips64、powerpc/powerpc64、riscv32/riscv64 和 s390x 等,许多之前存在工具链问题、标准库支持缺失或崩溃的情况现在应该可以正常工作了。此外,该版本在构建系统方面进行了大量升级,并对语言进行了多项重要改进,例如引入了 Labeled Switch 和 Decl Literals 等新特性。为了缩短编辑/编译/调试周期,版本还迈向了两个长期投资目标:增量编译和快速 x86 后端。 -## [Zig 如何对不同目标平台的开发支持进行分级?]($section.id('target-support-tiers')) +# [Zig 如何对不同目标平台的开发支持进行分级?]($section.id('target-support-tiers')) Zig 使用四层系统来对不同目标平台的支持级别进行分类,其中 Tier 1 是最高级别: @@ -139,15 +139,15 @@ Zig 使用四层系统来对不同目标平台的支持级别进行分类,其 - **Tier 3:** 编译器可以依赖外部后端(如 LLVM)为该目标平台生成机器代码。链接器可以为该目标平台生成目标文件、库和可执行文件。 - **Tier 4:** 编译器可以依赖外部后端(如 LLVM)为该目标平台生成汇编源代码。如果 LLVM 将此目标平台视为实验性,则需要从源代码构建 LLVM 和 Zig 才能使用它。 -## [什么是 Labeled Switch,它有什么优势?]($section.id('labeled-switch-advantages')) +# [什么是 Labeled Switch,它有什么优势?]($section.id('labeled-switch-advantages')) Labeled Switch 是 Zig 0.14.0 中引入的一项语言特性,允许 switch 语句被标记,并作为 continue 语句的目标。continue :label value 语句会用 value 替换原始的 switch 表达式操作数,并重新评估 switch。尽管在语义上类似于循环中的 switch,但 Labeled Switch 的关键优势在于其代码生成特性。它可以生成帮助 CPU 更准确预测分支的代码,从而提高热循环中的性能,特别是在处理指令分派、评估有限状态自动机 (FSA) 或执行类似基于 case 的评估时。这有助于 branch predictor 更准确地预测控制流。 -## [Decl Literals 是什么,它解决了哪些问题?]($section.id('decl-literals-explanation')) +# [Decl Literals 是什么,它解决了哪些问题?]($section.id('decl-literals-explanation')) Decl Literals 是 Zig 0.14.0 扩展 "enum literal" 语法 (.foo) 而引入的新特性。现在,一个枚举字面量 .foo 不仅可以引用枚举变体,还可以使用 Result Location Semantics 引用目标类型上的任何声明(const/var/fn)。这在初始化结构体字段时特别有用,可以避免重复指定类型,并有助于避免 Faulty Default Field Values 的问题,确保数据不变量不会因覆盖单个字段而受到破坏。它也支持直接调用函数来初始化值。 -## [Zig 0.14.0 版本在内存分配器方面有哪些值得关注的变化?]($section.id('allocator-changes')) +# [Zig 0.14.0 版本在内存分配器方面有哪些值得关注的变化?]($section.id('allocator-changes')) 该版本对内存分配器进行了多项改进: @@ -156,7 +156,7 @@ Decl Literals 是 Zig 0.14.0 扩展 "enum literal" 语法 (.foo) 而引入的新 - **Allocator API Changes (remap):** std.mem.Allocator.VTable 引入了一个新的 remap 函数,允许尝试扩展或收缩内存并可能重新定位,如果无法在不执行内部 memcpy 的情况下完成,则返回 null,提示调用者自行处理复制。同时,resize 函数保持不变。Allocator.VTable 中的所有函数现在使用 std.mem.Alignment 类型代替 u8,增加了类型安全。 - **Runtime Page Size:** 移除了编译时已知的 std.mem.page\_size,代之以编译时已知的页面大小上下界 std.heap.page\_size\_min 和 std.heap.page\_size\_max。运行时获取页面大小可以使用 std.heap.pageSize(),它会优先使用编译时已知的值,否则在运行时查询操作系统并缓存结果。这修复了对 Asahi Linux 等新硬件上运行 Linux 的支持。 -## [Zig 0.14.0 版本如何改进构建系统,特别是处理模块和依赖关系?]($section.id('build-system-improvements')) +# [Zig 0.14.0 版本如何改进构建系统,特别是处理模块和依赖关系?]($section.id('build-system-improvements')) Zig 0.14.0 版本在构建系统方面有多项重要改进: @@ -166,7 +166,7 @@ Zig 0.14.0 版本在构建系统方面有多项重要改进: - **addLibrary Function:** 引入 addLibrary 函数作为 addSharedLibrary 和 addStaticLibrary 的替代,允许在 build.zig 中更容易地切换链接模式,并与 linkLibrary 函数名称保持一致。 - **Import ZON:** ZON 文件现在可以在编译时通过 @import("foo.zon") 导入,前提是结果类型已知。 -## [Zig 0.14.0 版本在编译器后端和编译速度方面有哪些进展?]($section.id('compiler-backend-and-speed')) +# [Zig 0.14.0 版本在编译器后端和编译速度方面有哪些进展?]($section.id('compiler-backend-and-speed')) 该版本在编译器后端和编译速度方面取得了进展: @@ -174,7 +174,7 @@ Zig 0.14.0 版本在构建系统方面有多项重要改进: - **Incremental Compilation:** 引入了增量编译特性,可以通过 -fincremental 标志启用。尽管尚未默认启用,但结合文件系统监听,可以显著缩短修改代码后的重新分析时间,提供快速的编译错误反馈。 - **x86 Backend:** x86 后端在行为测试套件中的通过率已接近 LLVM 后端,并且在开发时通常比 LLVM 后端提供更快的编译速度和更好的调试器支持。虽然尚未默认选中,但鼓励用户尝试使用 -fno-llvm 或在构建脚本中设置 use\_llvm = false 来启用。 -## [Zig 0.14.0 版本在工具链和运行时方面有哪些值得注意的更新?]($section.id('toolchain-and-runtime-updates')) +# [Zig 0.14.0 版本在工具链和运行时方面有哪些值得注意的更新?]($section.id('toolchain-and-runtime-updates')) 该版本在工具链和运行时方面也有多项更新: @@ -189,6 +189,6 @@ Zig 0.14.0 版本在构建系统方面有多项重要改进: - **Optimized memcpy:** 提供了优化的 memcpy 实现,不再捆绑 musl 的 memcpy 文件。 - **Integrated Fuzzer:** 集成了 alpha 质量的 fuzzer,可以通过 --fuzz CLI 选项使用,并提供一个 fuzzer Web UI 显示实时代码覆盖率。 -## [总结]($section.id('conclusion')) +# [总结]($section.id('summary')) Zig 0.14.0 版本是向 1.0.0 版本迈进的重要一步,在性能优化(尤其是编译速度)、跨平台支持、语言特性和标准库方面都带来了显著改进。增量编译和快速 x86 后端是关键的长期投资,旨在提升开发者体验。新语言特性如 Labeled Switch 和 Decl Literals 提供了更强大和安全的编程模式。标准库的重组和容器的调整反映了社区的使用模式和最佳实践。构建系统也获得了重要升级,使模块管理和依赖处理更加灵活。尽管仍存在已知 bug,但 Zig 社区在本次发布中展示了活跃的开发和持续的进步。 diff --git a/content/post/2023-09-05-bog-gc-1-en.smd b/content/post/2023-09-05-bog-gc-1-en.smd index b5cced9..238d2a7 100644 --- a/content/post/2023-09-05-bog-gc-1-en.smd +++ b/content/post/2023-09-05-bog-gc-1-en.smd @@ -10,11 +10,11 @@ }, --- -## [Bog GC Design]($section.id('bog-gc-design')) +# [Bog GC Design]($section.id('bog-gc-design')) Bog is a small scripting language developed using Zig. Its GC design is inspired by a paper titled [An efficient of Non-Moving GC for Function languages](https://www.pllab.riec.tohoku.ac.jp/papers/icfp2011UenoOhoriOtomoAuthorVersion.pdf). -## [Overview]($section.id('bog-gc-design')) +# [Overview]($section.id('Overview')) 1. Introduction - Design of the Heap @@ -22,7 +22,7 @@ Bog is a small scripting language developed using Zig. Its GC design is inspired - Design of Bitmap 2. Implementation -## [Introduction]($section.id('bog-gc-design')) +# [Introduction]($section.id('Introduction')) GC stands for garbage collection, which is primarily a memory management strategy for the `heap` region. Memory allocations in the heap are done in exponentially increasing sizes, with a special sub-heap dedicated solely to very large objects. One advantage of this approach might be its efficiency in handling memory requests of various sizes. @@ -71,13 +71,13 @@ graph TD +------------+ ``` -### [Memory Sub-Heap Pool]($section.id('bog-gc-design')) +## [Memory Sub-Heap Pool]($section.id('Memory Sub-Heap Pool')) We've designed a memory resource pool. This pool consists of numerous allocation segments of fixed size. It means that, regardless of how much space a sub-heap requests, it will request it in units of these fixed-size "segments". For instance, if the allocation segments in the pool are of size 1MB, then a sub-heap might request space in sizes of 1MB, 2MB, 3MB, etc., rather than requesting non-integer multiples like 1.5MB or 2.5MB. The dynamic allocation and reclaiming of space by the sub-heaps from a resource pool made up of fixed-size segments provide greater flexibility and may enhance the efficiency of memory utilization. -### [Types of GC]($section.id('bog-gc-design')) +## [Types of GC]($section.id('Types of GC')) There are many common types of GC. @@ -136,7 +136,7 @@ However, Non-Moving GC has its disadvantages: To address these problems requires many complicated steps, which won't be elaborated on here. We'll focus on Bog's GC for the explanation. -### [Meta Bitmap]($section.id('bog-gc-design')) +## [Meta Bitmap]($section.id('Meta Bitmap')) "Meta Bitmap" or "meta-level bitmaps". This is a higher-level bitmap that summarizes the contents of the original bitmap. This hierarchical structure is similar to the inode mapping in file systems or the use of multi-level page tables in computer memory management. @@ -150,11 +150,11 @@ Let's design for a 32-bit architecture. A 32-bit architecture means that the com For example, if a bitmap is 320 bits long, then on a 32-bit architecture, the worst-case scenario might require checking 10 blocks of 32 bits to find a free bit. This can be represented by log32(320), which results in 10. -### [Bitmap]($section.id('bog-gc-design')) +## [Bitmap]($section.id('Bitmap')) Since Bog's GC is essentially still based on "Mark-Sweep", using bitmaps to record data is indispensable. In Bog, we adopted the method of "bitmap records data" for GC. And to improve efficiency, we introduced the concept of meta-bitmaps, where every 4 elements correspond to a meta-bitmap, recording the occupancy status of multiple spaces, and increasing the depth based on the object age in the heap. -### [Implementation]($section.id('bog-gc-design')) +## [Implementation]($section.id('implementation')) In reality, Bog's design is a bit more complex. Here are sample in practical code: diff --git a/content/post/2023-09-05-bog-gc-1.smd b/content/post/2023-09-05-bog-gc-1.smd index 58f5731..c869763 100644 --- a/content/post/2023-09-05-bog-gc-1.smd +++ b/content/post/2023-09-05-bog-gc-1.smd @@ -10,11 +10,11 @@ }, --- -## [Bog GC Design -- Concepts]($section.id('bog-gc-design-concepts')) +# [Bog GC Design -- Concepts]($section.id('bog-gc-design-concepts')) [Bog](https://github.com/Vexu/bog) 是一款基于 Zig 开发的小型脚本语言。它的 GC 设计受到一篇论文[An efficient of Non-Moving GC for Function languages](https://www.pllab.riec.tohoku.ac.jp/papers/icfp2011UenoOhoriOtomoAuthorVersion.pdf)的启发。 -## [Summary]($section.id('summary')) +# [Summary]($section.id('summary')) GC 是一种垃圾回收的机制,主要是针对`heap`区域的内存管理策略。在堆中的内存分配是按照指数级增长的大小进行的,此外还有一个专门用于非常大对象的特殊子堆。这种方法的一个优点可能是它可以高效地处理各种大小的内存请求。 @@ -63,13 +63,13 @@ graph TD +------------+ ``` -### [Memory Sub-Heap Pool]($section.id('memory-sub-heap-pool')) +## [Memory Sub-Heap Pool]($section.id('memory-sub-heap-pool')) 我们设计一个内存的资源池。这个池由许多固定大小的分配段组成。这意味着,无论子堆请求多少空间,它都会以这些固定大小的"段"为单位来请求。例如,如果池中的分配段大小为 1MB,那么一个子堆可能会请求 1MB、2MB、3MB 等大小的空间,而不是请求 1.5MB 或 2.5MB 这样的非整数倍的大小。 而子堆从一个由固定大小的段组成的资源池中动态分配和回收空间,这种策略可以提供更高的灵活性,并可能提高内存使用的效率。 -### [Types of GC]($section.id('types-of-gc')) +## [Types of GC]($section.id('types-of-gc')) 常见的 GC 有很多类型。 @@ -123,7 +123,7 @@ Cheney 的拷贝收集器: 是一种用于半区(semi-space)的拷贝垃圾 为了解决这些问题需要很多复杂的步骤,在此不多赘述。单以 Bog 的 GC 来讲解。 -### [Meta Bitmap]($section.id('meta-bitmap')) +## [Meta Bitmap]($section.id('meta-bitmap')) "元级位图"或"meta-level bitmaps"。这是一个更高级别的位图,用于汇总原始位图的内容。这种层次化的结构类似于文件系统中的 inode 映射或多级页表在计算机内存管理中的使用。 @@ -137,11 +137,11 @@ Cheney 的拷贝收集器: 是一种用于半区(semi-space)的拷贝垃圾 例如,如果一个位图有 320 位,那么在 32 位架构上,最坏的情况可能需要检查 10 个 32 位块才能找到一个空闲位。这可以通过 $\log_{32}(320)$来表示,结果是 10。 -### [Bitmap]($section.id('bitmap')) +## [Bitmap]($section.id('bitmap')) 由于 Bog 的 GC 本质上还是采用了"标记-清除",所以利用位图来记录数据是必不可少的。在 Bog 中,我们采用了"位图记录数据"的方式来进行 GC。而为了提高效率,我们增加了元位图的概念,即每 4 个元素对应一个元位图,用于记录多空间的占用状态,并且根据 heap 的对象时间增加深度。 -### [Implementation]($section.id('implementation')) +## [Implementation]($section.id('implementation')) 实际上,在 Bog 的设计中,要更加复杂一些。我们增加了 diff --git a/content/post/2023-09-05-hello-world.smd b/content/post/2023-09-05-hello-world.smd index 5ecb6fa..133b7d6 100644 --- a/content/post/2023-09-05-hello-world.smd +++ b/content/post/2023-09-05-hello-world.smd @@ -11,7 +11,7 @@ - [ZigCC 网站](https://ziglang.cc) - [ZigCC 公众号](https://github.com/zigcc/.github/raw/main/zig_mp.png) -## [供稿方式]($section.id('供稿方式')) +# [供稿方式]($section.id('供稿方式')) TODO @@ -27,6 +27,6 @@ date: '2023-09-05T16:13:13+0800' --- ``` -## [本地预览]($section.id('本地预览')) +# [本地预览]($section.id('local-preview')) 在写完文章后,可以使用 [Hugo](https://gohugo.io/) 进行本地预览,只需在项目根目录执行 `hugo server`,这会启动一个 HTTP 服务,默认的访问地址是: http://localhost:1313/ diff --git a/content/post/2023-09-21-zig-midi.smd b/content/post/2023-09-21-zig-midi.smd index 8053b2f..c79e869 100644 --- a/content/post/2023-09-21-zig-midi.smd +++ b/content/post/2023-09-21-zig-midi.smd @@ -23,7 +23,7 @@ MIDI 是"乐器数字接口"的缩写,是一种用于音乐设备之间通信 ├── midi.zig ``` -## [基础]($section.id('basic')) +# [基础]($section.id('basic')) 在 MIDI 协议中,`0xFF` 是一个特定的状态字节,用来表示元事件(Meta Event)的开始。元事件是 MIDI 文件结构中的一种特定消息,通常不用于实时音频播放,但它们包含有关 MIDI 序列的元数据,例如序列名称、版权信息、歌词、时间标记、速度(BPM)更改等。 @@ -56,7 +56,7 @@ MIDI 是"乐器数字接口"的缩写,是一种用于音乐设备之间通信 元事件主要存在于 MIDI 文件中,特别是在标准 MIDI 文件 (SMF) 的上下文中。在实时 MIDI 通信中,元事件通常不会被发送,因为它们通常不会影响音乐的实际播放。 -## [Midi.zig]($section.id('Midi.zig')) +# [Midi.zig]($section.id('Midi.zig')) 本文件主要是处理 MIDI 消息的模块,为处理 MIDI 消息提供了基础结构和函数。 @@ -194,7 +194,7 @@ pub const Message = struct { - value 和 setValue 函数:用于获取和设置 MIDI 消息的值字段。 - Kind 枚举:定义了 MIDI 消息的所有可能种类,包括通道事件和系统事件。 -### [midi 消息结构]($section.id('midi 消息结构')) +## [midi 消息结构]($section.id('midi 消息结构')) 我们需要先了解 MIDI 消息的一些背景。 @@ -211,7 +211,7 @@ pub const Message = struct { 4. **设置值**:`message.values = .{ ... }` 将这两个 7 位的值设置到 `message.values` 中。 -### [事件]($section.id('事件')) +## [事件]($section.id('事件')) 针对事件,我们看 enum。 @@ -251,7 +251,7 @@ pub const Message = struct { 以下是对每个枚举成员的简要说明: -#### [频道事件 (Channel events)]($section.id('频道事件 (Channel events)')) +### [频道事件 (Channel events)]($section.id('频道事件 (Channel events)')) 1. **NoteOff**:这是一个音符结束事件,表示某个音符不再播放。 2. **NoteOn**:这是一个音符开始事件,表示开始播放某个音符。 @@ -261,7 +261,7 @@ pub const Message = struct { 6. **ChannelPressure**:频道压力事件,与多声道键盘压力相似,但它适用于整个频道,而不是特定音符。 7. **PitchBendChange**:音高弯曲变更事件,表示音符音高的上升或下降。 -#### [系统事件 (System events)]($section.id('系统事件 (System events)')) +### [系统事件 (System events)]($section.id('系统事件 (System events)')) 1. **ExclusiveStart**:独占开始事件,标志着一个独占消息序列的开始。 2. **MidiTimeCodeQuarterFrame**:MIDI 时间码四分之一帧,用于同步与其他设备。 @@ -276,11 +276,11 @@ pub const Message = struct { 11. **ActiveSensing**:活动感知事件,是一种心跳信号,表示设备仍然在线并工作。 12. **Reset**:重置事件,用于将设备重置为其初始状态。 -#### [其他]($section.id('其他')) +### [其他]($section.id('其他')) 1. **Undefined**:未定义事件,可能表示一个未在此枚举中定义的或无效的 MIDI 事件。 -## [decode.zig]($section.id('decode.zig')) +# [decode.zig]($section.id('decode.zig')) 本文件是对 MIDI 文件的解码器, 提供了一组工具,可以从不同的输入源解析 MIDI 文件的各个部分。这样可以方便地读取和处理 MIDI 文件。 @@ -498,7 +498,7 @@ pub fn file(reader: anytype, allocator: *mem.Allocator) !midi.File { 8. trackEvent: 从 reader 中解析一个 MIDI 轨道事件。它可以是 MIDI 消息或元事件。 9. file: 用于从 reader 解码一个完整的 MIDI 文件。它首先解码文件头,然后解码所有的文件块。这个函数会返回一个表示 MIDI 文件的结构体。 -### [message 解析]($section.id('message 解析')) +## [message 解析]($section.id('message 解析')) ```zig const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { @@ -531,7 +531,7 @@ const status_byte = if (statusByte(first_byte.?)) |status_byte| blk: { 4. `else return error.InvalidMessage;`: 如果 `first_byte` 不是状态字节,并且不存在前一个消息,那么返回一个 `InvalidMessage` 错误。 -## [encode.zig]($section.id('encode.zig')) +# [encode.zig]($section.id('encode.zig')) 本文件用于将 MIDI 数据结构编码为其对应的二进制形式。具体来说,它是将内存中的 MIDI 数据结构转换为 MIDI 文件格式的二进制数据。 @@ -672,7 +672,7 @@ pub fn file(writer: anytype, f: midi.File) !void { - file 函数:这是主函数,用于将整个 MIDI 文件数据结构编码为其二进制形式。它首先编码文件头,然后循环编码每个块和块中的事件。 -### [int 函数]($section.id('int 函数')) +## [int 函数]($section.id('int 函数')) ```zig @@ -731,7 +731,7 @@ pub fn int(writer: anytype, i: u28) !void { - 使用提供的`writer`将翻转后的字节写入到目标位置。 -## [file.zig]($section.id('file.zig')) +# [file.zig]($section.id('file.zig')) 主要目的是为了表示和处理 MIDI 文件的不同部分,以及提供了一个迭代器来遍历 MIDI 轨道的事件。 @@ -912,7 +912,7 @@ pub const TrackIterator = struct { - 定义了一个 Result 结构来返回事件和关联的数据。 - 提供了一个`next`方法来读取下一个事件。 -## [Build.zig]($section.id('Build.zig')) +# [Build.zig]($section.id('build-zig')) buid.zig 是一个 Zig 构建脚本(build.zig),用于配置和驱动 Zig 的构建过程。 diff --git a/content/post/2023-12-24-zig-build-explained-part1.smd b/content/post/2023-12-24-zig-build-explained-part1.smd index b3e846a..7fba69d 100644 --- a/content/post/2023-12-24-zig-build-explained-part1.smd +++ b/content/post/2023-12-24-zig-build-explained-part1.smd @@ -15,11 +15,11 @@ Zig 构建系统仍然缺少文档,对很多人来说,这是不使用它的 我们将从一个刚刚初始化的 Zig 项目开始,逐步深入到更复杂的项目。在此过程中,我们将学习如何使用库和软件包、添加 C 代码,甚至如何创建自己的构建步骤。 -## [免责声明]($section.id('免责声明')) +# [免责声明]($section.id('免责声明')) 由于我不会解释 Zig 语言的语法或语义,因此我希望你至少已经有了一些使用 Zig 的基本经验。我还将链接到标准库源代码中的几个要点,以便您了解所有这些内容的来源。我建议你阅读编译系统的源代码,因为如果你开始挖掘编译脚本中的函数,大部分内容都不言自明。所有功能都是在标准库中实现的,不存在隐藏的构建魔法。 -## [开始]($section.id('开始')) +# [开始]($section.id('开始')) 我们通过新建一个文件夹来创建一个新项目,并在该文件夹中调用 zig init-exe。 @@ -56,7 +56,7 @@ pub fn build(b: *std.Build) void { } ``` -## [基础知识]($section.id('基础知识')) +# [基础知识]($section.id('基础知识')) 构建系统的核心理念是,Zig 工具链将编译一个 Zig 程序 (build.zig),该程序将导出一个特殊的入口点(`pub fn build(b: *std.build.Builder) void`),当我们调用 `zig build` 时,该入口点将被调用。 @@ -95,7 +95,7 @@ Step 遵循与 std.mem.Allocator 相同的接口模式,需要实现一个 make 现在,我们需要创建一个稍正式的 Zig 程序: -## [编译 Zig 源代码]($section.id('编译 Zig 源代码')) +# [编译 Zig 源代码]($section.id('编译 Zig 源代码')) 要使用编译系统编译可执行文件,编译器需要使用函数 Builder.addExecutable,它将为我们创建一个新的 LibExeObjStep。这个步骤实现是 zig build-exe、zig build-lib、zig build-obj 或 zig test 的便捷封装,具体取决于初始化方式。本文稍后将对此进行详细介绍。 @@ -118,7 +118,7 @@ pub fn build(b: *std.build.Builder) void { 这将始终在当前机器的调试模式下编译,因此对于初学者来说,这可能就足够了。但如果你想开始发布你的项目,你可能需要启用交叉编译: -## [交叉编译]($section.id('交叉编译')) +# [交叉编译]($section.id('交叉编译')) 交叉编译是通过设置程序的目标和编译模式来实现的 @@ -187,7 +187,7 @@ zig build -Doptimize=ReleaseSmall 但我们仍然必须调用 zig build 编译,因为默认调用仍然没有任何作用。让我们改变一下! -## [安装工件]($section.id('安装工件')) +# [安装工件]($section.id('安装工件')) 要安装任何东西,我们必须让它依赖于构建器的安装步骤。该步骤是已创建的,可通过 Builder.getInstallStep() 访问。我们还需要创建一个新的 InstallArtifactStep,将我们的 exe 文件复制到安装目录(通常是 zig-out) @@ -254,7 +254,7 @@ pub fn build(b: *std.build.Builder) void { 现在,从理解初始构建脚本到完全扩展,还缺少一个部分: -## [运行已构建的应用程序]($section.id('运行已构建的应用程序')) +# [运行已构建的应用程序]($section.id('运行已构建的应用程序')) 为了开发用户体验和一般便利性,从构建脚本中直接运行程序是非常实用的。这通常是通过运行步骤实现的,可以通过 zig build run 调用。 @@ -328,7 +328,7 @@ pub fn build(b: *std.build.Builder) void { zig build run -- -o foo.bin foo.asm ``` -## [结论]($section.id('结论')) +# [结论]($section.id('conclusion')) 本系列的第一章应该能让你完全理解本文开头的构建脚本,并能创建自己的构建脚本。 diff --git a/content/post/2023-12-28-zig-build-explained-part2.smd b/content/post/2023-12-28-zig-build-explained-part2.smd index 3736639..44a44bd 100644 --- a/content/post/2023-12-28-zig-build-explained-part2.smd +++ b/content/post/2023-12-28-zig-build-explained-part2.smd @@ -9,15 +9,15 @@ > - 原文链接: https://zig.news/xq/zig-build-explained-part-2-1850 > - API 适配到 Zig 0.11.0 版本 -## [注释]($section.id('注释')) +# [注释]($section.id('注释')) 从现在起,我将只提供一个最精简的 build.zig,用来说明解决一个问题所需的步骤。如果你想了解如何将所有这些文件粘合到一个构建文件中,请阅读本系列[第一篇文章](2023-12-24-zig-build-explained-part1)。 -## [在命令行上编译 C 代码]($section.id('在命令行上编译 C 代码')) +# [在命令行上编译 C 代码]($section.id('在命令行上编译 C 代码')) Zig 有两种编译 C 代码的方法,而且这两种很容易混淆。 -### [使用 zig cc]($section.id('使用 zig cc')) +## [使用 zig cc]($section.id('使用 zig cc')) Zig 提供了 LLVM c 编译器 clang。第一种是 zig cc 或 zig c++,它是与 clang 接近 1:1 的前端。由于我们无法直接从 build.zig 访问这些功能(而且我们也不需要!),所以我将在快速的介绍这个主题。 @@ -41,7 +41,7 @@ zig cc -o example.exe -target x86_64-windows-gnu buffer.c main.c 如你所见,只需向 -target 传递目标三元组,就能调用交叉编译。只需确保所有外部库都已准备好进行交叉编译即可! -## [使用 zig build-exe 和其他工具]($section.id('使用 zig build-exe 和其他工具')) +# [使用 zig build-exe 和其他工具]($section.id('使用 zig build-exe 和其他工具')) 使用 Zig 工具链构建 C 项目的另一种方法与构建 Zig 项目的方法相同: @@ -59,7 +59,7 @@ zig build-exe -lc -target x86_64-windows-gnu main.c buffer.c 你会发现,使用这条编译命令,Zig 会自动在输出文件中附加 .exe 扩展名,并生成 .pdb 调试数据库。如果你在此处传递 --name example,输出文件也会有正确的 .exe 扩展名,所以你不必考虑这个问题。 -## [用 build.zig 创建 C 代码]($section.id('用 build.zig 创建 C 代码')) +# [用 build.zig 创建 C 代码]($section.id('用 build.zig 创建 C 代码')) 那么,我们如何用 build.zig 来构建上面的两个示例呢? @@ -112,15 +112,15 @@ exe.addCSourceFile(.{ .file = std.build.LazyPath.relative("buffer.c"), .flags = exe.addCSourceFile(.{.file = std.build.LazyPath.relative("buffer.c"), .flags = &.{"-fno-sanitize=undefined"}}); ``` -## [使用外部库]($section.id('使用外部库')) +# [使用外部库]($section.id('使用外部库')) 通常情况下,C 项目依赖于其他库,这些库通常预装在 Unix 系统中,或通过软件包管理器提供。 为了演示这一点,我们创建一个小工具,通过 curl 库下载文件,并将文件内容打印到标准输出: ```c -##include -##include +#include +#include static size_t writeData(void *ptr, size_t size, size_t nmemb, FILE *stream) { size_t written; @@ -181,7 +181,7 @@ zig build ./zig-out/bin/downloader https://mq32.de/public/ziggy.txt ``` -## [配置路径]($section.id('配置路径')) +# [配置路径]($section.id('配置路径')) 由于我们不能在交叉编译项目中使用 pkg-config,或者我们想使用预编译的专用库(如 BASS 音频库),因此我们需要配置包含路径和库路径。 @@ -220,7 +220,7 @@ pub fn build(b: *std.Build) void { addIncludePath 和 addLibraryPath 都可以被多次调用,以向编译器添加多个路径。这些函数不仅会影响 C 代码,还会影响 Zig 代码,因此 @cImport 可以访问包含路径中的所有头文件。 -## [每个文件的包含路径]($section.id('每个文件的包含路径')) +# [每个文件的包含路径]($section.id('每个文件的包含路径')) 因此,如果我们需要为每个 C 文件设置不同的包含路径,我们就需要用不同的方法来解决这个问题: 由于我们仍然可以通过 addCSourceFile 传递任何 C 编译器标志,因此我们也可以在这里手动设置包含目录。 @@ -252,7 +252,7 @@ pub fn build(b: *std.Build) void { 上面的示例非常简单,所以你可能会想为什么需要这样的东西。答案是,有些库的头文件名称非常通用,如 api.h 或 buffer.h,而您希望使用两个共享头文件名称的不同库。 -## [构建 C++ 项目]($section.id('构建 C++ 项目')) +# [构建 C++ 项目]($section.id('构建 C++ 项目')) 到目前为止,我们只介绍了 C 文件,但构建 C++ 项目并不难。你仍然可以使用 addCSourceFile,但只需传递一个具有典型 C++ 文件扩展名的文件,如 cpp、cxx、c++ 或 cc: @@ -285,7 +285,7 @@ pub fn build(b: *std.Build) void { 这就是构建 C++ 文件所需的全部知识,没有什么更神奇的了。 -## [指定语言版本]($section.id('指定语言版本')) +# [指定语言版本]($section.id('指定语言版本')) 试想一下,如果你创建了一个庞大的项目,其中的 C 或 C++ 文件有新有旧,而且可能是用不同的语言标准编写的。为此,我们可以使用编译器标志来传递 -std=c90 或 -std=c++98: @@ -320,7 +320,7 @@ pub fn build(b: *std.Build) void { } ``` -## [条件编译]($section.id('条件编译')) +# [条件编译]($section.id('条件编译')) 与 Zig 相比,C 和 C++ 的条件编译方式非常繁琐。由于缺乏惰性求值的功能,有时必须根据目标环境来包含/排除文件。你还必须提供宏定义来启用/禁用某些项目功能。 @@ -373,7 +373,7 @@ pub fn build(b: *std.Build) void { 有条件地包含文件就像使用 if 一样简单,你可以这样做。只要不根据你想在构建脚本中定义的任何约束条件调用 addCSourceFile 即可。只包含特定平台的文件?看看上面的脚本就知道了。根据系统时间包含文件?也许这不是个好主意,但还是有可能的! -## [编译大型项目]($section.id('编译大型项目')) +# [编译大型项目]($section.id('编译大型项目')) 由于大多数 C(更糟糕的是 C++)项目都有大量文件(SDL2 有 411 个 C 文件和 40 个 C++ 文件),我们必须找到一种更简单的方法来编译它们。调用 addCSourceFile 400 次并不能很好地扩展。 @@ -478,7 +478,7 @@ pub fn build(b: *std.build.Builder) !void { 注意:其他构建系统会考虑文件名,而 Zig 系统不会!例如,在一个 qmake 项目中不能有两个名为 data.c 的文件!Zig 并不在乎,你可以添加任意多的同名文件,只要确保它们在不同的文件夹中就可以了 😏。 -## [编译 Objective C]($section.id('编译 Objective C')) +# [编译 Objective C]($section.id('编译 Objective C')) 我完全忘了!Zig 不仅支持编译 C 和 C++,还支持通过 clang 编译 Objective C! @@ -514,7 +514,7 @@ pub fn build(b: *std.Build) void { 在这里,链接 libc 是隐式的,因为添加框架会自动强制链接 libc。是不是很酷? -## [混合使用 C 和 Zig 源代码]($section.id('混合使用 C 和 Zig 源代码')) +# [混合使用 C 和 Zig 源代码]($section.id('混合使用 C 和 Zig 源代码')) 现在,是最后一章: 混合 C 代码和 Zig 代码! @@ -554,7 +554,7 @@ pub fn build(b: *std.Build) void { 您应用程序的入口点现在必须在 Zig 代码中,因为根文件必须导出一个 pub fn main(...) ……。 因此,如果你想将 C 项目中的代码移植到 Zig 中,你必须将 argc 和 argv 转发到你的 C 代码中,并将 C 代码中的 main 重命名为其他函数(例如 oldMain),然后在 Zig 中调用它。如果需要 argc 和 argv,可以通过 std.process.argsAlloc 获取。或者更好: 在 Zig 中重写你的入口点,然后从你的项目中移除一些 C 语言! -## [结论]($section.id('结论')) +# [结论]($section.id('conclusion')) 假设你只编译一个输出文件,那么现在你应该可以将几乎所有的 C/C++ 项目移植到 build.zig。 diff --git a/content/post/2023-12-29-zig-build-explained-part3.smd b/content/post/2023-12-29-zig-build-explained-part3.smd index 08dea9d..e7a962e 100644 --- a/content/post/2023-12-29-zig-build-explained-part3.smd +++ b/content/post/2023-12-29-zig-build-explained-part3.smd @@ -11,17 +11,17 @@ 从现在起,我将只提供一个最精简的 build.zig,用来说明解决一个问题所需的步骤。如果你想了解如何将所有这些文件粘合到一个构建文件中,请阅读本系列[第一篇文章](2023-12-24-zig-build-explained-part1)。 -## [复合项目]($section.id('复合项目')) +# [复合项目]($section.id('复合项目')) 有很多简单的项目只包含一个可执行文件。但是,一旦开始编写库,就必须对其进行测试,通常会编写一个或多个示例应用程序。当人们开始使用外部软件包、C 语言库、生成代码等时,复杂性也会随之上升。 本文试图涵盖所有这些用例,并将解释如何使用 build.zig 来编写多个程序和库。 -## [软件包]($section.id('软件包')) +# [软件包]($section.id('软件包')) 译者:此处代码和说明,需要 zig build-exe --pkg-begin,但是在 0.11 已经失效。所以删除。 -## [库]($section.id('库')) +# [库]($section.id('库')) 但 Zig 也知道库这个词。但我们不是已经讨论过外部库了吗? @@ -36,7 +36,7 @@ 在 Zig 中,我们需要导入库的头文件,如果头文件在 Zig 中,则使用包,如果是 C 语言头文件,则使用 @cImport。 -## [工具]($section.id('工具')) +# [工具]($section.id('工具')) 如果我们的项目越来越多,那么在构建过程中就需要使用工具。这些工具通常会完成以下任务: @@ -48,7 +48,7 @@ 但我们如何在 build.zig 中完成这些工作呢? -## [添加软件包]($section.id('添加软件包')) +# [添加软件包]($section.id('添加软件包')) 添加软件包通常使用 LibExeObjStep 上的 addPackage 函数。该函数使用一个 std.build.Pkg 结构来描述软件包的外观: @@ -98,7 +98,7 @@ exe.addModule("lola",pkgs.lola); exe.addModule("args",pkgs.args); ``` -## [添加库]($section.id('添加库')) +# [添加库]($section.id('添加库')) 添加库相对容易,但我们需要配置更多的路径。 @@ -106,7 +106,7 @@ exe.addModule("args",pkgs.args); 假设我们要将 libcurl 链接到我们的项目,因为我们要下载一些文件。 -### [系统库]($section.id('系统库')) +## [系统库]($section.id('系统库')) 对于 unixoid 系统,我们通常可以使用系统软件包管理器来链接系统库。方法是调用 linkSystemLibrary,它会使用 pkg-config 自行找出所有路径: @@ -137,7 +137,7 @@ pub fn build(b: *std.Build) void { 对于 Linux 系统,这是链接外部库的首选方式。 -### [本地库]($section.id('本地库')) +## [本地库]($section.id('本地库')) 不过,您也可以链接您作为二进制文件提供商的库。为此,我们需要调用几个函数。首先,让我们来看看这样一个库是什么样子的: @@ -167,7 +167,7 @@ include 我们可以看到,vendor/libcurl/include 路径包含我们的头文件,vendor/libcurl/lib 文件夹包含一个静态库(libcurl.a)和一个共享/动态库(libcurl.so)。 -### [动态链接]($section.id('动态链接')) +## [动态链接]($section.id('动态链接')) 要链接 libcurl,我们需要先添加 include 路径,然后向 zig 提供库的前缀和库名:(todo 代码有待验证,因为 curl 可能需要自己编译自己生成 static lib) @@ -197,7 +197,7 @@ addLibraryPath 对库文件也有同样的作用。这意味着 Zig 现在也会 最后,linkSystemLibrary 会告诉 Zig 搜索名为 "curl "的库。如果你留心观察,就会发现上面列表中的文件名是 libcurl.so,而不是 curl.so。在 unixoid 系统中,库文件的前缀通常是 lib,这样就不会将其传递给系统。在 Windows 系统中,库文件的名字应该是 curl.lib 或类似的名字。 -## [静态链接]($section.id('静态链接')) +# [静态链接]($section.id('静态链接')) 当我们要静态链接一个库时,我们必须采取一些不同的方法: @@ -250,7 +250,7 @@ pub fn build(b: *std.build.Builder) void { 我们可以继续静态链接越来越多的库,并拉入完整的依赖关系树。 -## [通过源代码链接库]($section.id('通过源代码链接库')) +# [通过源代码链接库]($section.id('通过源代码链接库')) 不过,我们还有一种与 Zig 工具链截然不同的链接库方式: @@ -293,7 +293,7 @@ pub fn build(b: *std.build.Builder) void { 这一点尤其方便,因为我们可以使用 setTarget 和 setBuildMode 从任何地方编译到任何地方。 -## [使用工具]($section.id('使用工具')) +# [使用工具]($section.id('使用工具')) 在工作流程中使用工具,通常是在需要以 bison、flex、protobuf 或其他形式进行预编译时。工具的其他用例包括将输出文件转换为不同格式(如固件映像)或捆绑最终应用程序。 @@ -352,7 +352,7 @@ size 是一个很好的工具,它可以输出有关可执行文件代码大小 如您所见,我们在这里使用了 addArtifactArg,因为 addSystemCommand 只会返回一个 std.build.RunStep。这样,我们就可以增量构建完整的命令行,包括任何 LibExeObjStep 输出、FileSource 或逐字参数。 -## [全新工具]($section.id('全新工具')) +# [全新工具]($section.id('全新工具')) 最酷的是 我们还可以从 LibExeObjStep 获取 std.build.RunStep: @@ -389,7 +389,7 @@ pub fn build(b: *std.build.Builder) void { 调用 zig build pack 时,我们将运行 tools/pack.zig。这很酷,因为我们还可以从头开始编译所需的工具。为了获得最佳的开发体验,你甚至可以从源代码编译像 bison 这样的 "外部 "工具,这样就不会依赖系统了! -## [将所有内容放在一起]($section.id('将所有内容放在一起')) +# [将所有内容放在一起]($section.id('将所有内容放在一起')) 一开始,所有这些都会让人望而生畏,但如果我们看一个更大的 build.zig 实例,就会发现一个好的构建文件结构会给我们带来很大帮助。 @@ -491,7 +491,7 @@ pub fn build(b: *std.build.Builder) void { 两者都是为了在主机平台上运行,而不是在目标机器上。 此外,deploy_tool 还设置了固定的编译模式,因为我们希望快速编译,即使我们编译的是应用程序的调试版本。 -## [总结]($section.id('总结')) +# [总结]($section.id('summary')) 看完这一大堆文字,你现在应该可以构建任何你想要的项目了。我们已经学会了如何编译 Zig 应用程序,如何为其添加任何类型的外部库,甚至如何为发布管理对应用程序进行后处理。 diff --git a/content/post/2024-01-12-how-to-release-your-zig-applications.smd b/content/post/2024-01-12-how-to-release-your-zig-applications.smd index 26764c6..09e5d58 100644 --- a/content/post/2024-01-12-how-to-release-your-zig-applications.smd +++ b/content/post/2024-01-12-how-to-release-your-zig-applications.smd @@ -14,7 +14,7 @@ You've just written an application in Zig and want others to use it. A convenient way for users to use your application is to provide a pre-built executable file. Next, I'll discuss the two main things that need to be handled correctly in a good release process. -## [Why provide pre-built executable files?]($section.id('Why provide pre-built executable files?')) +# [Why provide pre-built executable files?]($section.id('Why provide pre-built executable files?')) Given how C/C++ dependencies work (or don't work), for some C/C++ projects, providing pre-compiled executable files is almost a necessity, @@ -26,7 +26,7 @@ That said, the more popular your application is, the less users will care what l Your users don't want to install Zig and run the build process to easily use your application (99% of the time, the rest 1% will be discussed later), so it's still better to pre-build your application. -## [`zig build` vs `zig build-exe`]($section.id('`zig build` vs `zig build-exe`')) +# [`zig build` vs `zig build-exe`]($section.id('`zig build` vs `zig build-exe`')) In this article, we'll see how to make, release a build for a Zig project, so it's worth spending a little time to fully understand the relationship between Zig build system and command line. @@ -48,7 +48,7 @@ what `zig build` does is to prepare command line parameters for `build-exe`. This means that in compiling Zig code, `zig build` (assuming the code in `build.zig` is correct) and `zig build-exe` are one-to-one correspondence. The only difference is convenience. -## [Build mode]($section.id('Build mode')) +# [Build mode]($section.id('Build mode')) When building a Zig project with `zig build` or `zig build-exe myapp.zig`, the default is a debug build executable file. Debug build is mainly for development convenience, so it's usually considered unsuitable for release. @@ -70,7 +70,7 @@ but it's not prioritized for performance, but rather for trying to minimize the For example, this is a very meaningful build mode for WebAssembly, because you want the executable file to be as small as possible, and the sandbox runtime environment has provided a lot of security guarantees. -## [How to set build mode]($section.id('How to set build mode')) +# [How to set build mode]($section.id('How to set build mode')) When using `zig build-exe`, you can add `-O ReleaseSafe` (or `ReleaseFast`, or `ReleaseSmall`) to get the corresponding build mode. @@ -98,7 +98,7 @@ const exe = b.addExecutable(.{ This is how you specify release mode in command line: `zig build -Doptimize=ReleaseSafe` (or `-Doptimize=ReleaseFast`, or `-Doptimize=ReleaseSmall`). -## [Choose the correct build target]($section.id('Choose the correct build target')) +# [Choose the correct build target]($section.id('Choose the correct build target')) Now, we've chosen the correct release mode, it's time to consider build target. Obviously, if the platform used and build platform are different, the corresponding build target needs to be specified, @@ -109,7 +109,7 @@ The most straightforward way is to directly call `zig build` or `zig build-exe` If you do this, sometimes it works, but sometimes it crashes due to `illegal instruction` (or similar error). What's happening? -## [CPU features]($section.id('CPU features')) +# [CPU features]($section.id('CPU features')) If build target is not specified when building, Zig will build for the current machine, which means it will use all instruction sets supported by the current CPU. If the CPU supports AVX extension, @@ -173,7 +173,7 @@ const exe = b.addExecutable(.{ This also means if you want to add other restrictions or change how to specify target when building, you can achieve this by adding your own code. -## [Conclusion]($section.id('Conclusion')) +# [Conclusion]($section.id('conclusion')) Now you've learned the things you need to ensure correctly when releasing a build: choose a release optimization mode and choose the correct build target, including building for the same system you're building. diff --git a/content/post/2024-04-06-zig-cpp.smd b/content/post/2024-04-06-zig-cpp.smd index 0713036..7a9afe3 100644 --- a/content/post/2024-04-06-zig-cpp.smd +++ b/content/post/2024-04-06-zig-cpp.smd @@ -8,7 +8,7 @@ 尽管 Zig 社区宣称 Zig 语言是一个更好的 C (better C),但是我个人在学习 Zig 语言时经常会"触类旁通"C++。在这里列举一些例子来说明我的一些体会,可能会有一些不正确的地方,欢迎批评指正。 -## ["元能力" vs "元类型"]($section.id('"元能力" vs "元类型"')) +# ["元能力" vs "元类型"]($section.id('"元能力" vs "元类型"')) 在我看来,C++的增强方式是希望赋予语言一种"元能力",能够让人重新发明新的类型,使得使用 C++的程序员使用自定义的类型,进行一种类似于"领域内语言"(DSL)编程。一个通常的说法就是 C++中任何类型定义都像是在模仿基本类型`int`。比如我们有一种类型 T,那么我们则需要定义 T 在以下几种使用场景的行为: @@ -26,7 +26,7 @@ x = y; //x的类型是T,复制运算符重载,当然也有可能是移动运 不过话说回来,很多底层系统的开发需求往往和这种类型系统的构建相悖,比如如果你的类型就是一个`int`的封装,那么即使发生拷贝你也无所谓性能开销。但是如果是一个`struct`,那么通常情况下,你会比较 care 拷贝,而可能考虑"移动"之类的手段。这个时候各种 C++的提供的幻觉,就成了程序员开发的绊脚石,经常你需要分析一段 C++表达式里到底有没有发生拷贝,他是左值还是右值,其实你在写 C 语言的时候也很少去考虑了这些,你在 Zig 里同样也不需要。 -## [类型系统]($section.id('类型系统')) +# [类型系统]($section.id('类型系统')) C 语言最大弊病就是没有提供标准库,C++的标准库你要是能看懂,得具备相当的 C++的语法知识,但是 Zig 的标准库几乎不需要文档就能看懂。这其实是因为,在 C++里,类型不是一等成员(first class member),因此实现一些模版元编程算法特别不直观。但是在 Zig 里,`type`就是一等成员,比如你可以写: @@ -63,7 +63,7 @@ Some::OutputType 相当于对于 InputType 调用一个 Some"函数",然后输出一个 OutputType。 -## [命令式 VS 声明式]($section.id('命令式 VS 声明式')) +# [命令式 VS 声明式]($section.id('命令式 VS 声明式')) 比如实现一个函数,输入一个 bool 值,根据 bool 值,如果为真,那么输出 type A,如果为假那么输出 type B。 @@ -163,6 +163,6 @@ pub fn main() void { 即这个也是完全命令式的。当然 C++20 之后也出现了`if constexpr`和`concept`来进一步简化模版元编程,C++的元编程也在向命令式的方向进化。 -## [结束语]($section.id('结束语')) +# [结束语]($section.id('conclusion')) 尽管 Zig 目前"还不成熟",但是学习 Zig,如果采用一种对照的思路,偶尔也会"触类旁通"C++,达到举一反三的效果。 diff --git a/content/post/2024-05-07-package-hash.smd b/content/post/2024-05-07-package-hash.smd index ac1cd22..0267ce5 100644 --- a/content/post/2024-05-07-package-hash.smd +++ b/content/post/2024-05-07-package-hash.smd @@ -6,11 +6,11 @@ .draft = false, --- -## [package hash]($section.id('package-hash')) +# [package hash]($section.id('package-hash')) > 原文地址:[build.zig.zon dependency hashes](https://zig.news/michalsieron/buildzigzon-dependency-hashes-47kj) -## [引言]($section.id('引言')) +# [引言]($section.id('引言')) 作者 Michał Sieroń 最近在思考 `build.zig.zon` 中的依赖项哈希值的问题。这些哈希值都有相同的前缀,而这对加密哈希函数来说极其不同寻常。习惯性使用 Conda 和 Yocto 对下载的压缩包运行 sha256sum,但生成的摘要与 `build.zig.zon` 中的哈希值完全不同。 @@ -34,7 +34,7 @@ 以上摘取自 [hexops/mach](https://github.com/hexops/mach/blob/bffc66800584123e2844c4bc4b577940806f9088/build.zig.zon#L13-L26) 项目。 -## [初步探索]($section.id('初步探索')) +# [初步探索]($section.id('初步探索')) 经过一番探索,我找到了一个文档:[doc/build.zig.zon.md](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/doc/build.zig.zon.md),似乎没有任何线索指向它。而文档中对哈希有段简短的描述。 @@ -44,7 +44,7 @@ 该哈希值是基于一系列文件内容计算得出的,这些文件是在获取URL后并应用了路径规则后得到的。 这个字段是最重要的;一个包是的唯一性是由它的哈希值确定的,不同的 URL 可能对应同一个包。 -## [多重哈希]($section.id('多重哈希')) +# [多重哈希]($section.id('多重哈希')) 在他们的网站上有一个很好的可视化展示,说明了这一过程: [多重哈希](https://multiformats.io/multihash/)。 @@ -52,7 +52,7 @@ 因此 `build.zig.zon` 中的哈希字段不仅包含了摘要(digest),还包含了一些元数据(metadata)。但即使我们丢弃了头部信息,得到的结果仍与下载的 `tar` 包的 `sha256` 摘要不相符。而这就涉及到了包含规则的问题。 -## [包含规则(inclusion rules)]($section.id('包含规则(inclusion rules)')) +# [包含规则(inclusion rules)]($section.id('包含规则(inclusion rules)')) 回到 [doc/build.zig.zon.md](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/doc/build.zig.zon.md) 文件,我们看到: @@ -93,7 +93,7 @@ pub fn includePath(self: Filter, sub_path: []const u8) bool { 除此之外,这个函数会检查 `sub_path` 是否被明确列出,或者是已明确列出的目录的子目录。 -## [计算哈希]($section.id('计算哈希')) +# [计算哈希]($section.id('计算哈希')) 现在我们知道了 `build.zig.zon` 的包含规则,也知道使用了 `SHA256` 算法。但我们仍然不知道实际的哈希结果是如何得到的。例如,它可能是通过将所有包含的文件内容输入哈希器来计算的。所以让我们再仔细看看,也许我们可以找到答案。 @@ -121,7 +121,7 @@ try all_files.append(hashed_file); 跟踪 `workerHashFile`,我们看到它是 `hashFileFallible` 的一个简单包装,而后者看起来相当复杂。让我们来分解一下。 -## [单个文件的哈希计算]($section.id('单个文件的哈希计算')) +# [单个文件的哈希计算]($section.id('单个文件的哈希计算')) 首先,会进行一些初始化设置,其中创建并用规整后的文件路径初始化了一个新的哈希器实例: @@ -169,7 +169,7 @@ hasher.update(link_name); 首先进行路径分隔符的规整,保证不同平台一致,之后将符号链接的目标路径输入 `hasher`。在 `hashFileFallible` 函数最后,把计算出的哈希值赋值给 `HashedFile` 对象的 `hash` 字段。 -## [组合哈希]($section.id('组合哈希')) +# [组合哈希]($section.id('组合哈希')) 尽管有了单个文件的哈希值,但我们仍不知道如何得到最终的哈希。幸运的是,曙光就在眼前。 @@ -199,13 +199,13 @@ for (all_files.items) |hashed_file| { 在这里我们看到所有计算出的哈希被一个接一个地输入到一个新的哈希器中。在 `computeHash` 的最后,我们返回 `hasher.finalResult()`,现在我们明白哈希值是如何获得的了。 -## [最终多哈希值]($section.id('最终多哈希值')) +# [最终多哈希值]($section.id('最终多哈希值')) 现在我们有了一个 `SHA256` 摘要,可以最终返回到 `main.zig`,在那里我们调用 [`Package.Manifest.hexDigest(fetch.actual_hash)`](https://github.com/ziglang/zig/blob/9d64332a5959b4955fe1a1eac793b48932b4a8a8/src/Package/Manifest.zig#L174)。在那里,我们将多哈希头写入缓冲区,之后是我们的组合摘要。 顺便说一下,我们看到所有哈希头都是 `1220` 并非巧合。这是因为 `Zig` [硬编码了 SHA256](https://github.com/ziglang/zig/blob/1a6485d111d270014f57c46c53597a516a24dc47/src/Package/Manifest.zig#L3) - 0x12,它有 32 字节的摘要 - 0x20。 -## [总结]($section.id('总结')) +# [总结]($section.id('总结')) 总结一下:最终哈希值是一个多哈希头 + `SHA256` 摘要。 @@ -215,7 +215,7 @@ for (all_files.items) |hashed_file| { 在实验这个之后,我有一个想法,我很惊讶 Zig 没有检查 `build.zig.zon` 中列出的所有文件是否存在。但这可能是另一天的话题了。 -## [译者注]($section.id('译者注')) +# [译者注]($section.id('translators-note')) 在使用本地包时,可以使用下面的命令进行 hash 问题的排查: diff --git a/content/post/2024-05-24-interface-idioms.smd b/content/post/2024-05-24-interface-idioms.smd index f742595..916fd21 100644 --- a/content/post/2024-05-24-interface-idioms.smd +++ b/content/post/2024-05-24-interface-idioms.smd @@ -8,7 +8,7 @@ > 原文链接: https://zig.news/yglcode/code-study-interface-idiomspatterns-in-zig-standard-libraries-4lkj -## [引言]($section.id('引言')) +# [引言]($section.id('引言')) 在 Java 和 Go 中,可以使用“接口”(一组方法或方法集)定义基于行为的抽象。通常接口包含所谓的虚表(`vtable`) 以实现动态分派。Zig 允许在结构体、枚举、联合和不透明类型中声明函数和方法,尽管 Zig 尚未支持接口作为一种语言特性。 @@ -31,7 +31,7 @@ Zig 标准库应用了一些代码习语或模式以达到类似效果。 完整代码位于[此仓库](https://github.com/yglcode/zig_interfaces),你可以使用 `zig test interfaces.zig` 运行它。 -## [背景设定]($section.id('背景设定')) +# [背景设定]($section.id('背景设定')) 让我们使用经典的面向对象编程示例,创建一些形状:点(`Point`)、盒子(`Box`)和圆(`Circle`)。 @@ -86,7 +86,7 @@ fn init_data() struct { point: Point, box: Box, circle: Circle } { } ``` -## [接口1:枚举标签联合]($section.id('接口1:枚举标签联合')) +# [接口1:枚举标签联合]($section.id('接口1:枚举标签联合')) Loris Cro 在[“使用 Zig 0.10.0 轻松实现接口”](https://zig.news/kristoff/easy-interfaces-with-zig-0100-2hc5) 中介绍了使用枚举标签联合作为接口的方法。这是最简单的解决方案,尽管你必须在联合类型中显式列出所有“实现”该接口的变体类型。 @@ -126,7 +126,7 @@ test "union_as_intf" { } ``` -## [接口2:vtable 和动态分派的第一种实现]($section.id('接口2:vtable 和动态分派的第一种实现')) +# [接口2:vtable 和动态分派的第一种实现]($section.id('接口2:vtable 和动态分派的第一种实现')) Zig 已从最初基于嵌入式 `vtab` 和 `#fieldParentPtr()` 的动态分派切换到基于“胖指针”接口的以下模式; 请查阅此文章了解更多细节[“Allocgate 将在 Zig 0.9 中到来...”](https://pithlessly.github.io/allocgate.html)。 @@ -198,7 +198,7 @@ test "vtab1_as_intf" { } ``` -## [接口3:vtable 和动态分派的第二种实现]($section.id('接口3:vtable 和动态分派的第二种实现')) +# [接口3:vtable 和动态分派的第二种实现]($section.id('接口3:vtable 和动态分派的第二种实现')) 在上述第一种实现中,通过 `Shape2.init()` 将 `Box` “转换”为接口 `Shape2` 时,会对 `box` 实例进行类型检查, 以确保其实现了 `Shape2` 的方法(包括名称的匹配签名)。第二种实现中有两个变化: @@ -271,7 +271,7 @@ test "vtab2_as_intf" { } ``` -## [接口4:使用嵌入式 vtab 和 @fieldParentPtr() 的原始动态分派]($section.id('接口4:使用嵌入式 vtab 和 @fieldParentPtr() 的原始动态分派')) +# [接口4:使用嵌入式 vtab 和 @fieldParentPtr() 的原始动态分派]($section.id('接口4:使用嵌入式 vtab 和 @fieldParentPtr() 的原始动态分派')) 接口 `std.build.Step` 和所有构建步骤 `std.build.[RunStep, FmtStep, ...]` 仍然使用这种模式。 @@ -364,7 +364,7 @@ test "vtab3_embedded_in_struct" { } ``` -## [接口5:编译时的泛型接口]($section.id('接口5:编译时的泛型接口')) +# [接口5:编译时的泛型接口]($section.id('interface-5-compile-time-generic-interface')) 所有上述接口都侧重于 `vtab` 和动态分派:接口值将隐藏其持有的具体值的类型。因此,你可以将这些接口值放入数组中并统一处理。 diff --git a/content/post/2024-06-10-zig-hashmap-1.smd b/content/post/2024-06-10-zig-hashmap-1.smd index 26d242b..ba84b9f 100644 --- a/content/post/2024-06-10-zig-hashmap-1.smd +++ b/content/post/2024-06-10-zig-hashmap-1.smd @@ -24,7 +24,7 @@ pub fn put(self: *Self, key: K, value: V) Allocator.Error!void { 正如我所说,大部分繁重的工作都由 `std.HashMapUnmanaged` 完成,其他变体通过一个名为 `unmanaged` 的字段对其进行封装。 -## [Unmanaged]($section.id('Unmanaged')) +# [Unmanaged]($section.id('Unmanaged')) 在Zig标准库中随处可见的类型命名约定是 `unmanaged`。这种命名方式表明所涉及的类型不维护 `allocator`。任何需要分配内存的方法都会显式地将 `allocator` 作为参数传递。要实际看到这一点,可以考虑下面这个链表的例子: @@ -143,7 +143,7 @@ pub fn LinkedList(comptime T: type) type { 为了简化,本文的其余部分不会再提到 `unmanaged`。我们看到关于 `StringHashMap` 或 `AutoHashMap` 或 `HashMap` 的任何内容同样适用于它们的 Unmanaged 变体。 -## [HashMap 与 AutoHashMap]($section.id('HashMap 与 AutoHashMap')) +# [HashMap 与 AutoHashMap]($section.id('HashMap 与 AutoHashMap')) std.HashMap 是一个泛型类型,它接受两个类型参数:键的类型和值的类型。正如我们所见,哈希映射需要两个函数:hash 和 eql。这两个函数合起来被称为“上下文(context)”。这两个函数都作用于键,并且没有一个单一的 hash 或 eql 函数适用于所有类型。例如,对于整数键,eql 将是 `a_key == b_key`;而对于 `[]const u8` 键,我们希望使用 `std.mem.eql(u8, a_key, b_key)`。 @@ -234,7 +234,7 @@ pub const StringContext = struct { }; ``` -## [自定义上下文]($section.id('自定义上下文')) +# [自定义上下文]($section.id('自定义上下文')) 我们将在第一部分结束时,直接使用 `HashMap`,这意味着提供我们自己的上下文。我们将从一个简单的例子开始:为不区分大小写的 ASCII 字符串创建一个 `HashMap`。我们希望以下内容输出:`Goku = 9000`。请注意,虽然我们使用键 `GOKU` 进行插入,但我们使用“goku”进行获取: @@ -379,7 +379,7 @@ pub fn hash(_: HashContext, u: User) u64 { 插入这两个函数,以上示例应该可以工作。 -## [结论]($section.id('结论')) +# [结论]($section.id('conclusion')) 希望你现在对 Zig 的哈希表的实现以及如何在代码中利用它们有了更好的理解。在大多数情况下,`std.StringHashMap` 或 `std.AutoHashMap` 就足够了。但知道 `*Unmanaged` 变体的存在和目的,以及更通用的 `std.HashMap`,可能会派上用场。如果没有其他用途,现在文档和它们的实现应该更容易理解了。 diff --git a/content/post/2024-06-11-zig-hashmap-2.smd b/content/post/2024-06-11-zig-hashmap-2.smd index ef3f82e..dac855e 100644 --- a/content/post/2024-06-11-zig-hashmap-2.smd +++ b/content/post/2024-06-11-zig-hashmap-2.smd @@ -41,7 +41,7 @@ keys: values: 这个基本的可视化表示将贯穿本文大部分内容,并且不断强调条目的位置需要保持一致性和可预测性。即使哈希表需要在增长时从一个底层数组移动到另一个(即当填充因子达到一定阈值并要求扩大以容纳更多数据时),这一事实是我们将反复回顾的主题。 -## [值管理]($section.id('值管理')) +# [值管理]($section.id('值管理')) 如果我们对上述代码片段进行扩展,并调用 `lookup.get("Paul")`,返回的值将是 `1234`。在处理像 `i32` 这样的原始类型时,很难直观地理解 `get` 方法和它的可选返回类型 `?i32` 或更通用的 `?V`(其中 `V` 表示任何值类型)之间的区别。考虑到这一点,让我们通过替换 `i32` 为一个封装了更多信息的 `User` 类型来展示这一概念: @@ -224,7 +224,7 @@ while (it.next()) |value_ptr| { 在最后一种情况下,由于我们存储的是 `User` 而不是 `*User`,我们的 `value_ptr` 是指向 `User` 的指针(不像之前那样是指向指针的指针)。 -## [Keys]($section.id('Keys')) +# [Keys]($section.id('Keys')) 我们可以开始和结束这一节:我们关于值的所有内容同样适用于键。这是100%正确的,但这在某种程度上不太直观。大多数开发人员很快就能理解,存储在哈希表中的堆分配的 `User` 实例有其自身的生命周期,需要显式管理/释放。但由于某些原因,这对于键来说并不那么明显。 @@ -337,7 +337,7 @@ if (lookup.fetchRemove(user.name)) |kv| { 对于大多数情况,在处理非原始键或值时,关键是当你调用哈希表的 `deinit` 时,你为键和值分配的任何内存不会被自动释放;你需要自己处理。 -## [getOrPut]($section.id('getOrPut')) +# [getOrPut]($section.id('getOrPut')) 虽然我们已经讨论过的内容有很多含义,但对我来说,直接暴露键和值指针的最大好处之一是 `getOrPut` 方法。 @@ -367,7 +367,7 @@ if (gop.found_existing) { 当然,只要不对哈希表进行修改,`value_ptr` 就应被视为有效。顺便提一句,这同样适用于我们通过 `iterator()`、`valueIterator` 和 `keyIterator` 获取的迭代键和值,原因相同。 -## [结论]($section.id('结论')) +# [结论]($section.id('conclusion')) 希望你现在对使用「std.HashMap」、「std.AutoHashMap」和「std.StringHashMap」以及它们的「unmanaged」变体感到更加得心应手。虽然你可能永远不需要提供自己的上下文(例如「hash」和「eql」函数),但了解这是一个选项是有益的。在日常编程中,可视化数据尤其有用,尤其是在使用指针和添加间接层次时。每当我处理 `value_ptr` 或 `key_ptr` 时,我都会想到这些切片以及值或键与这些切片中值或键的实际地址之间的区别。 diff --git a/content/post/2024-08-12-zoop.smd b/content/post/2024-08-12-zoop.smd index 8d0794d..fe939ce 100644 --- a/content/post/2024-08-12-zoop.smd +++ b/content/post/2024-08-12-zoop.smd @@ -6,17 +6,17 @@ .draft = false, --- -## [zoop]($section.id('zoop')) +# [zoop]($section.id('zoop')) zoop 是 zig 的一个 OOP 解决方案,详细信息可以看看 [zoop官网](https://zhuyadong.github.io/zoop-docs/)。 -## [为什么不用别的 OOP 语言]($section.id('为什么不用别的 OOP 语言')) +# [为什么不用别的 OOP 语言]($section.id('为什么不用别的 OOP 语言')) 简单的说,是我个人原因,必需使用 zig 的同时,还一定要用 OOP,所以有了 zoop。 -## [zoop 入门]($section.id('zoop 入门')) +# [zoop 入门]($section.id('zoop 入门')) -## [类和方法]($section.id('类和方法')) +# [类和方法]($section.id('类和方法')) ```zig pub const Base = struct { @@ -73,7 +73,7 @@ pub const Base = struct { 上面的代码给 `Base` 添加了一个可以继承的方法 `getName()`。 -## [类的继承]($section.id('类的继承')) +# [类的继承]($section.id('类的继承')) zoop 引入一个关键字 `extends` 用来实现继承,比如下面我们定义 `Base` 的子类 `Child`: @@ -102,7 +102,7 @@ test { } ``` -## [接口定义]($section.id('接口定义')) +# [接口定义]($section.id('接口定义')) zoop 中的接口,实际上是一个胖指针。下面我们定义一个接口 `IGetName`: @@ -132,7 +132,7 @@ pub const IGetName = struct { 上面的代码具体原理下面会说到,这里大家知道接口就是这样定义的就行了。上面的代码定义了接口 `IGetName`,这个接口有一个方法 `getName()`。 -## [接口实现]($section.id('接口实现')) +# [接口实现]($section.id('接口实现')) 上面的 `Base` 类正好也有个符合 `IGetName` 接口的方法 `getName()`,那我们修改一下 `Base` 的代码让它来实现 `IGetName` 接口: @@ -148,7 +148,7 @@ pub const Base = struct { 可以看到实现接口和继承用的同样一个关键字 `extends`。因为子类会继承父类的接口,所以这样一来,`Child` 也自动实现了 `IGetName` 接口。 -## [方法重写和虚函数调用]($section.id('方法重写和虚函数调用')) +# [方法重写和虚函数调用]($section.id('方法重写和虚函数调用')) 我们修改上面 `Child` 的代码,重写 `getName()` 方法: @@ -194,7 +194,7 @@ try t.expectEqualStrings(base.as(IGetName).?.getName(), "override"); 那么 zoop 的基本使用方法就介绍到这里,下面我们开始介绍 zoop 的实现原理。 -## [预设场景]($section.id('预设场景')) +# [预设场景]($section.id('预设场景')) 接下来的讨论基于如下的属于 `mymod` 模块的类和接口: @@ -285,7 +285,7 @@ pub const Child = struct { - `Base`: 基类,实现接口 `ISetName` - `Child`: 子类,继承 `Base`,并实现接口 `IGetName` -## [核心数据结构 `zoop.Mixin(T)`]($section.id('核心数据结构 `zoop.Mixin(T)`')) +# [核心数据结构 `zoop.Mixin(T)`]($section.id('核心数据结构 `zoop.Mixin(T)`')) 我们看看两个类的 `mixin` 这个数据里面有什么: @@ -339,7 +339,7 @@ zoop.Mixin(Child) = struct { 上面两个函数获取的都是最外层对象的数据。根据对 `mixin` 数据的分析,zoop 的类型转换的原理就很清楚了,大家可以参考官网上关于 [类型转换](https://zhuyadong.github.io/zoop-docs/guide/as-cast) 的内容。 -## [动态构造类的方法、接口方法、和 `Vtable`]($section.id('动态构造类的方法、接口方法、和 `Vtable`')) +# [动态构造类的方法、接口方法、和 `Vtable`]($section.id('动态构造类的方法、接口方法、和 `Vtable`')) OOP 概念中的继承,重写,虚函数,实质其实就是在编译时动态构造需要的方法和属性。zoop 中主要是通过通过 [zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple) 这个模块来进行编译时动态构造。 @@ -347,11 +347,11 @@ OOP 概念中的继承,重写,虚函数,实质其实就是在编译时动 下面我介绍一下 zoop 中用到的 `comptime` 一些技巧,相信会对大家今后使用 zig 有帮助。 -## [`struct` 很万能]($section.id('`struct` 很万能')) +# [`struct` 很万能]($section.id('`struct` 很万能')) `comptime` 编程中,`struct` 是你最好的朋友,想在不同的 `comptime` 函数之间传递数据,最方便的方式,就是通过构造一个 `struct`,把想传递的数据通过 `pub const xxx = ...` 的方式传递出去,通过 `struct` 保存数据最好的地方,就在于这个数据在运行时也是可用的 (`struct` 中的常量,是保存在 exe 的 `.data` 区,运行时可见),[zoop.tuple](https://zhuyadong.github.io/zoop-docs/reference/tuple) 就是通过这个方法实现的。 -## [动态构造 `struct` 的字段,用 `@Type()`]($section.id('动态构造 `struct` 的字段,用 `@Type()`')) +# [动态构造 `struct` 的字段,用 `@Type()`]($section.id('动态构造 `struct` 的字段,用 `@Type()`')) 网上好像很少有关于 `@Type()` 的使用说明,一般都是通过看 `zig.std` 的代码来学习,那我这里就稍微说明一下,希望能对大家有帮助。 目前 zig 通过 `@Type()`,能动态构造的 `struct`,只有纯字段类型的 `struct` (个人理解)。构造的方法,就是先把计算好的一个 `std.builtin.Type.StructField` 数组传递给 `@Type()` 来返回一个 `struct`,比如以下代码: @@ -398,7 +398,7 @@ const MyStruct = struct { zoop 动态构造 `Vtable` 就是通过这个方法做到的,参考 [zoop.DefVtable 原理](https://zhuyadong.github.io/zoop-docs/reference/principle#DefVtable) 和 [zoop 源代码](https://github.com/zhuyadong/zoop.git) -## [动态构造 `struct` 的函数,用 `usingnamespace`]($section.id('动态构造 `struct` 的函数,用 `usingnamespace`')) +# [动态构造 `struct` 的函数,用 `usingnamespace`]($section.id('动态构造 `struct` 的函数,用 `usingnamespace`')) 要想定义 `struct` 中的函数,理论上代码是一定要写在 `struct` 中的,目前 zig 唯一留下的一个口子,就是 `usingnamespace`,zoop 正是利用这个特性,来动态构造 `struct` 的函数。 @@ -442,7 +442,7 @@ pub usingnamespace struct { 因而实现了对 `Base.setName()` 方法的继承。 -## [运行时根据类型找 `Vtable` 和父类指针]($section.id('运行时根据类型找 `Vtable` 和父类指针')) +# [运行时根据类型找 `Vtable` 和父类指针]($section.id('finding-vtable-and-parent-class-pointers-at-runtime-based-on-type')) 这个功能的实现当时第一版是使用的 `std.StaticStringMap` 保存了一个类中所有接口名到接口 `Vtable` ,以及父类名到父类数据在本类中的地址偏移的映射。和 C++ 的 `dynamic_cast` 比起来,性能是比较差的。后来看到西瓜大大发的一个链接 [点这里](https://github.com/SuperAuguste/cursed-zig-errors),忽然意识到这不就是我一直想要的 `comptime` 全局变量么,我终于能写出 `typeId(comptime T: type) u32` 这样的函数了: diff --git a/content/post/2024-11-26-typed-fsm.smd b/content/post/2024-11-26-typed-fsm.smd index 984d732..8ae6d63 100644 --- a/content/post/2024-11-26-typed-fsm.smd +++ b/content/post/2024-11-26-typed-fsm.smd @@ -6,11 +6,11 @@ .draft = false, --- -## [typed fsm]($section.id('typed-fsm')) +# [typed fsm]($section.id('typed-fsm')) -## [1. 简单介绍类型化有限状态机的优势]($section.id('1. 简单介绍类型化有限状态机的优势')) +# [1. 简单介绍类型化有限状态机的优势]($section.id('1. 简单介绍类型化有限状态机的优势')) -## [1.1 介绍有限状态机]($section.id('1.1 介绍有限状态机')) +# [1.1 介绍有限状态机]($section.id('1.1 介绍有限状态机')) 有限状态机(FSM,以下简称状态机)是程序中很常见的设计模式。 @@ -18,7 +18,7 @@ 而状态主要是在代码层面帮助人们理解消息的产生和处理。 -## [1.2 [typed-fsm-zig](https://github.com/sdzx-1/typed-fsm-zig)]($section.id('1.2 [typed-fsm-zig](https://github.com/sdzx-1/typed-fsm-zig)')) +# [1.2 [typed-fsm-zig](https://github.com/sdzx-1/typed-fsm-zig) typed-fsm-zig 是一个利用 zig 类型系统加一些编程规范实现的一个库,用于实现类型安全的有限状态机。 @@ -39,13 +39,13 @@ typed-fsm-zig 是一个利用 zig 类型系统加一些编程规范实现的一 在实际的使用中没有任何的代码生成,除了一处隐式的约束要求之外,没有任何其它的控制,开发者完全掌握状态机,因此你可以方便的将它和你现有的代码结合起来。 -## [2. 例子:修改 ATM 状态机的状态]($section.id('2. 例子:修改 ATM 状态机的状态')) +# [2. 例子:修改 ATM 状态机的状态]($section.id('2. 例子:修改 ATM 状态机的状态')) 这里我将以一个 ATM 状态机(以下简称 ATM)的例子来展示 typed-fsm-zig 和 zig 的类型系统如何帮助我快速修改 ATM 的状态。 为了简单性,这里我不展示构建 ATM 这个例子的过程,感兴趣的可以在这里看到[代码](https://github.com/sdzx-1/typed-fsm-zig/blob/master/examples/atm-gui.zig)。 -## [2.1 介绍 ATM 状态机]($section.id('2.1 介绍 ATM 状态机')) +# [2.1 介绍 ATM 状态机]($section.id('2.1 介绍 ATM 状态机')) ATM 代表自动取款机,因此它的代码的逻辑就是模拟自动取款机的一些行为:插入银行卡,输入 pin,检查 pin,取钱,修改 pin。 @@ -76,7 +76,7 @@ ATM 代表自动取款机,因此它的代码的逻辑就是模拟自动取款 接下来的文章中我将修改 Update 的行为,并展示在这个过程中类型系统如何帮助我快速调整代码。 -## [2.2 修改 Update 消息]($section.id('2.2 修改 Update 消息')) +# [2.2 修改 Update 消息]($section.id('2.2 修改 Update 消息')) 实际的消息 Update 定义代码如下 @@ -134,7 +134,7 @@ referenced by: 在这里类型系统精确的告诉了我们需要修改的地方,以及原因。修改完成后程序即能正确运行。 -## [2.3 移除 changePin 状态]($section.id('2.3 移除 changePin 状态')) +# [2.3 移除 changePin 状态]($section.id('2.3 移除 changePin 状态')) 这一节中我们尝试移除 changePin 状态,看看类型系统会给我们什么反馈。 如果移除 changePin,新的状态图如下: @@ -177,7 +177,7 @@ examples/atm-gui.zig:296:10: error: no field named 'ChangePin' in enum '@typeInf 在这个过程中类型系统帮助我们找到问题和原因。这非常酷!!! -## [2.4 总结]($section.id('2.4 总结')) +# [2.4 总结]($section.id('2.4 总结')) 以上是一个简单的例子,展示了 typed-fsm-zig 对于提升状态机编程体验的巨大效果。 @@ -194,7 +194,7 @@ examples/atm-gui.zig:296:10: error: no field named 'ChangePin' in enum '@typeInf -## [3. 原理与实现]($section.id('3. 原理与实现')) +# [3. 原理与实现]($section.id('3. 原理与实现')) 最开始的版本是[typed-fsm](https://github.com/sdzx-1/typed-fsm),由使用 haskell 实现,它实现了完整类型安全的有限状态机。 @@ -434,7 +434,7 @@ fn s2Handler(val: Witness(Exmaple, .exit, .s2), ref: *i32) void { 以上就是 typed-fsm-zig 核心想法的完整介绍。接下来我将介绍需要的编程规范。 -## [4. typed-fsm-zig 需要哪些编程规范]($section.id('4. typed-fsm-zig 需要哪些编程规范')) +# [4. typed-fsm-zig 需要哪些编程规范]($section.id('4. typed-fsm-zig 需要哪些编程规范')) 1. 状态和消息集合之间需要满足的隐式命名规范 @@ -548,7 +548,7 @@ pub fn readyHandler(comptime w: AtmSt.EWitness(.ready), ist: *InternalState) voi 遵循这四点要求,就能获得强大的类型安全保证,足以让你愉快的使用状态机! -## [5. 接下来能够增强的功能]($section.id('5. 接下来能够增强的功能')) +# [5. 接下来能够增强的功能]($section.id('5-future-enhancements')) 暂时我能想到的有如下几点: diff --git a/content/post/2025-01-23-bonkers-comptime.smd b/content/post/2025-01-23-bonkers-comptime.smd index 78a55fb..24658b3 100644 --- a/content/post/2025-01-23-bonkers-comptime.smd +++ b/content/post/2025-01-23-bonkers-comptime.smd @@ -10,7 +10,7 @@ > 译注:原文中的代码块是交互式,翻译时并没有移植。另外,由于 comptime 本身即是关键概念,并且下文的意思更侧重于 Zig comptime 的特性,故下文大多使用 comptime 代替编译时概念。 -## [引子]($section.id('引子')) +# [引子]($section.id('引子')) 编程通过自动化地处理数据极大地提升了生产力。而元编程则让我们可以像处理数据一样处理代码,以此将编程的力量反向作用于编程自身。而在底层编程中,我想元编程可能带来最大的优势,因为那些高级概念必须得精确映射到某些低级操作。然而,除了函数式编程语言外,我一直觉得各编程语言对元编程的实现并不理想。因此,当我看到 Zig 把元编程列为一个主要特性时,我提起了很大的兴趣。 @@ -20,7 +20,7 @@ 为了明确起见,所有示例都是有效的 Zig 代码,但示例中的转换只是概念性的,它们并不是 Zig 实际的实现方式。 -## [视角0: 忽略它]($section.id('视角0: 忽略它')) +# [视角0: 忽略它]($section.id('视角0: 忽略它')) 我说我喜欢这个特性,却又立刻叫你忽略它,这确实有点怪。但我认为此处正是 Zig comptime 威力所体现的地方,所以我将从这里出发。Zig Zen 中的第三条是“倾向于阅读代码,而不是编写代码。”确实,能够轻松地阅读代码在各种情况下都很重要,因为它是建立概念理解的基础,而这种理解也是调试或修改代码所必需的。 @@ -71,7 +71,7 @@ pub fn main() void { Zig 中有很多基于 comptime 且远远不止这样简单的类型反射,但你只需要阅读那些代码、完全无需深入了解其中有关 comptime 的细节就可以理解它们在干什么。当然,如果你想使用 comptime 编写代码,则不能仅仅止步于此,让我们继续深入。 -## [视角1: 泛型]($section.id('视角1: 泛型')) +# [视角1: 泛型]($section.id('视角1: 泛型')) 泛型在 Zig 中并不是一个特定的功能。相反,Zig 中的仅仅一小部分的 comptime 特性就可以提供用来处理你进行泛型编程所需的一切。这种视角虽然不能让你完全理解 comptime,但它确实为你提供了一个入口点,借此,你可以完成基于元编程的许多任务。 @@ -121,7 +121,7 @@ pub fn main() void { 当然,也可以通过使用特殊类型 anytype 来推断参数的类型,而这通常在参数的类型对函数签名的其余部分没有影响时使用。(译注:此时要限制 a, b, c 的类型相同,所以此处不用 anytype ) -## [视角2:编译时运行的标准代码]($section.id('视角2:编译时运行的标准代码')) +# [视角2:编译时运行的标准代码]($section.id('视角2:编译时运行的标准代码')) 这是一个古老的故事: 增加一种自动执行命令的方法。当然,你还需要变量。 哦,还有条件。 拜托,能给我循环吗?这些看似合理的需求,最终导致这些自动化命令变得越来越复杂,甚至演变成一个完整的宏语言。 但 Zig 不同, 在运行时、编译时,甚至是构建系统中都使用了相同的语言。 @@ -178,7 +178,7 @@ pub fn main() !void { comptime 和运行时之间有一些小的区别。比如,只有 comptime 可以访问类型为 comptime_int、comptime_float 或 type 的变量。此外,一些函数只有 comptime 参数,这使它们仅限于编译时环境。相对的,只有运行时才能进行系统调用和那些依赖系统调用的函数。如果你的代码不使用这些特性,那么它在编译时和运行时中的表现将是一样的。 -## [视角3:[程序特化](https://en.wikipedia.org/wiki/Partial_application)]($section.id('视角3:[程序特化](https://en.wikipedia.org/wiki/Partial_application)')) +# [视角3:[程序特化](https://en.wikipedia.org/wiki/Partial_application) > 译者注:程序特化(Partial Evaluation)是一种编译优化技术,主要是:在编译期预先计算部分表达式或代码路径,以减少运行时计算开销,提前生成更具体的代码实现。 @@ -255,7 +255,7 @@ onst MyStruct = struct { 上面的示例是我们手动展开后的示例,但这项工作是由 Zig 的 comptime 完成的。这使得我们可以直接独立而完整地编写出我们要实现的功能,而不需要添加"当你改变 `MyStruct` 的字段时,记得更新 sum 函数"这样的由于依赖于 `MyStruct` 具体字段而预防功能失效的注释。 基于 comptime 的版本在 `MyStruct` 的任何字段变更时都可以正确地自动处理。 -## [视角4:Comptime 求值,运行时代码生成]($section.id('视角4:Comptime 求值,运行时代码生成')) +# [视角4:Comptime 求值,运行时代码生成]($section.id('视角4:Comptime 求值,运行时代码生成')) 这与程序特化(Partial Evaluation)非常相似。这里有两个版本的代码,输入(编译前)和输出(编译后)。输入代码由编译器运行。如果一个语句在编译时是可知的,它就会被直接求值。但是如果一个语句需要某些运行时的值,那么这个语句就会被添加到输出代码中。 @@ -305,7 +305,7 @@ const MyStruct = struct { 这样做的另一个后果是,Zig 代码的静态分析要比大多数静态类型语言复杂得多,因为编译器需要运行很大一部分才能确定所有类型。 因此,在 Zig 工具链跟上之前,代码自动补全等编辑工具并不总是能很好地发挥作用。 -## [视角5:直接生成代码(Textual Code Generation)]($section.id('视角5:直接生成代码(Textual Code Generation)')) +# [视角5:直接生成代码(Textual Code Generation)]($section.id('视角5:直接生成代码(Textual Code Generation)')) 我在文章开头感叹元编程难度。然而,即使在 Zig 中,它仍然是一个强大的工具,在解决某些问题方面也占有一席之地。如果您熟悉这种元编程方法,对 Zig comptime 提供的功能可能会觉得有些残缺。比如, 怎么在写一段代码在运行时能够生成新代码? @@ -368,13 +368,13 @@ pub fn writeMyStructOfType( 与本节相关的是文本宏,如 C 语言中的文本宏。你可以做的大多数正常事情都可以在 comptime 中完成,尽管它们很少采用类似的形式。 不过,文本宏并不能做所有允许做的事情。 例如,你不能决定不喜欢某个 Zig 关键字,然后让宏代替你自己的关键字。 我认为这是一个正确的决定,尽管对于那些习惯了这种能力的人来说,这是一个艰难的过渡。 此外,Zig 参考了半个世纪以来的程序员在这方面的探索,所以它的选择要理智得多。 -## [结论]($section.id('结论')) +# [结论]($section.id('结论')) 在阅读 Zig 代码以理解代码行为时,考虑 comptime 并不是必要的。而当编写 comptime 代码时,我通常会将其视为程序特化(Partial Evaluation)的一种形式。然而,如果你知道如何使用不同的元编程方法解决问题,你很可能有能力将其翻译成 comptime 形式。 元编程中直接生成代码的方法的存在,就是我全力支持 Zig 风格的 comptime 元编程的原因。尽管,直接生成代码是几乎是最强大的,但是,在阅读和调试时忽略 comptime 的特性的元编程方法确是最简单的。正因如此,我给本文取名为《Zig comptime 棒极了》。 -## [进一步阅读]($section.id('进一步阅读')) +# [进一步阅读]($section.id('further-reading')) Zig 并非一个仅仅依赖 comptime 这一特性的语言。你可以在[官方网站](https://ziglang.org/)上了解更多关于 Zig 的信息。 diff --git a/content/post/news/2023-12-11-first-meetup.smd b/content/post/news/2023-12-11-first-meetup.smd index c7202fe..ad8f810 100644 --- a/content/post/news/2023-12-11-first-meetup.smd +++ b/content/post/news/2023-12-11-first-meetup.smd @@ -45,7 +45,7 @@ Zig,目前主要有以下几个: 我们希望通过这些努力,提高 Zig 语言的知名度,完善 Zig 语言的生态,促进 Zig 语言的交流和学习。 -## [结论]($section.id('结论')) +# [结论]($section.id('conclusion')) Zig 中文社区第一次线上会议的召开,标志着 Zig 社区正式启航。如果读者对共建社区感兴趣,欢迎与我们联系。 diff --git a/content/post/news/2023-12-27-second-meetup.smd b/content/post/news/2023-12-27-second-meetup.smd index be608a3..ddcce3e 100644 --- a/content/post/news/2023-12-27-second-meetup.smd +++ b/content/post/news/2023-12-27-second-meetup.smd @@ -18,40 +18,40 @@ 这次会议主要是同步了之前会议落实的 action,主要是同步了不同项目的进展,由于临近年底,大家进度都不算太大,但还是有所进展,算是开了个好头😄 -## [项目进展]($section.id('项目进展')) +# [项目进展]($section.id('项目进展')) -## [Zig-OS](https://github.com/zigcc/zig-os) +# [Zig-OS](https://github.com/zigcc/zig-os) - 主要参与人员:西瓜 - 进展:粗略看完 rust 版本的教程;完成 freestanding 二进制,现在卡在了 bootloader 阶段 -## [Learn zig](https://github.com/learnzig/learnzig) +# [Learn zig](https://github.com/learnzig/learnzig) - 主要参与人员:金中甲 - zig的进阶特性,诸如构建系统、包管理、与C交互均已完成,目前教程内容已基本覆盖日常使用 - 增加了评论区的功能 - 待完成:反射(编译期反射和运行时反射)、内建函数说明(包含使用例子)、未定义行为、wasm、原子操作这些边缘部分 -## [Zig 教学视频]($section.id('Zig 教学视频')) +# [Zig 教学视频]($section.id('Zig 教学视频')) - 主要参与人员:Lambert - - 暂无明显进展 -## [Zig cookbook](https://github.com/zigcc/zig-cookbook) +# [Zig cookbook](https://github.com/zigcc/zig-cookbook) - 主要参与人员:夜白、西瓜 - 已经完成大部分内容 👍 -## [Zig 构建系统教程]($section.id('Zig 构建系统教程')) +# [Zig 构建系统教程]($section.id('Zig 构建系统教程')) - 主要参与人员:Reco - 目前主要是对 [zig build explained](https://zig.news/xq/zig-build-explained-part-3-1ima) 系列文章翻译 -## [新人介绍]($section.id('新人介绍')) +# [新人介绍]($section.id('new-member-introduction')) 在第一次会议后,有一些朋友想加入 ZigCC 社区,经过简单筛选,新增一名成员:Reco,下面是他的一些履历: diff --git a/content/post/news/2024-01-14-third-meetup.smd b/content/post/news/2024-01-14-third-meetup.smd index 4aa7143..e7024d6 100644 --- a/content/post/news/2024-01-14-third-meetup.smd +++ b/content/post/news/2024-01-14-third-meetup.smd @@ -18,7 +18,7 @@ - 公众号运营 - 如何与其他社区互动 -## [公众号运营]($section.id('公众号运营')) +# [公众号运营]($section.id('公众号运营')) 这是最近群里聊到的问题,由于 Zig 语言本身属于较新的技术,因此社区内资料比较少,这导致很多感兴趣的人没有一个好的学习途径。 @@ -40,12 +40,12 @@ 主要参与人员:西瓜、金中甲 -## [社区互动]($section.id('社区互动')) +# [社区互动]($section.id('社区互动')) 目前我们的成员在 Zig 的实践方面相对较少,因此决定目前不过多的去宣传,在积攒了一些实际项目经验后,再来考虑。 -## [欢迎更多朋友加入 ZigCC]($section.id('欢迎更多朋友加入 ZigCC')) +# [欢迎更多朋友加入 ZigCC]($section.id('welcome-more-friends-to-join-zigcc')) 现在回看,距离第一次 ZigCC 线上会议过了一个月,经过 ZigCC 成员的努力,还是交出了一份比较满意的答卷,[cookbook](https://github.com/zigcc/zig-cookbook) diff --git a/layouts/learn.shtml b/layouts/learn.shtml index 27d672b..bcfefec 100644 --- a/layouts/learn.shtml +++ b/layouts/learn.shtml @@ -4,6 +4,10 @@

    +
    +

    Table of Contents

    +
    +
    diff --git a/layouts/post.shtml b/layouts/post.shtml index e898fee..e332a3e 100644 --- a/layouts/post.shtml +++ b/layouts/post.shtml @@ -14,9 +14,5 @@ -
    -

    Table of Contents

    -
    -
    \ No newline at end of file diff --git a/layouts/templates/content.shtml b/layouts/templates/content.shtml index 6babbdc..5f63560 100644 --- a/layouts/templates/content.shtml +++ b/layouts/templates/content.shtml @@ -12,6 +12,10 @@
    +
    +

    Table of Contents

    +
    +
    From 0b1455bf7a0f97b96d911fcb45f3d8c5fa77c269 Mon Sep 17 00:00:00 2001 From: xihale Date: Sun, 6 Jul 2025 19:12:44 +0800 Subject: [PATCH 16/22] Refactor link conversion scripts by removing obsolete files and updating the source directory configuration. Standardize link formatting in the remaining script for improved clarity and consistency across content. --- .github/workflows/autocorrect.yml | 23 +++++ convert_link_format.js | 4 +- fix_links.js | 134 -------------------------- fix_links_advanced.js | 155 ------------------------------ remove_section_id_links.js | 102 -------------------- 5 files changed, 25 insertions(+), 393 deletions(-) create mode 100644 .github/workflows/autocorrect.yml delete mode 100644 fix_links.js delete mode 100644 fix_links_advanced.js delete mode 100644 remove_section_id_links.js diff --git a/.github/workflows/autocorrect.yml b/.github/workflows/autocorrect.yml new file mode 100644 index 0000000..4a88997 --- /dev/null +++ b/.github/workflows/autocorrect.yml @@ -0,0 +1,23 @@ +name: AutoCorrect CI + +on: + pull_request: + workflow_dispatch: + +jobs: + autocorrect: + runs-on: ubuntu-latest + steps: + - name: Check source code + uses: actions/checkout@v4 + + - name: AutoCorrect + uses: huacnlee/autocorrect-action@v2 + + - name: Review Dog + if: failure() + uses: huacnlee/autocorrect-action@v2 + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + reviewdog: true \ No newline at end of file diff --git a/convert_link_format.js b/convert_link_format.js index cac37a4..21a3d3b 100644 --- a/convert_link_format.js +++ b/convert_link_format.js @@ -5,7 +5,7 @@ const path = require('path'); // 配置 const config = { - sourceDir: './content/learn' + sourceDir: './content/' }; // 转换链接格式 @@ -56,7 +56,7 @@ function processDirectory(dirPath) { convertedCount += result.converted; totalCount += result.total; } else if (item.endsWith('.smd')) { - // 处理smd文件 + // 处理 smd 文件 const converted = processFile(fullPath); if (converted) convertedCount++; totalCount++; diff --git a/fix_links.js b/fix_links.js deleted file mode 100644 index 70e7f43..0000000 --- a/fix_links.js +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); - -// 配置 -const config = { - sourceDir: './content/learn' -}; - -// 修复链接格式 -function fixLinks(content) { - // 修复 [filename.md](filename.md) 格式到 $link.sub('filename') 格式 - // 这个正则表达式匹配 [filename.md](filename.md) 格式 - content = content.replace(/\[([^.\]]+)\.md\]\(([^.\]]+)\.md\)/g, (match, filename, linkFilename) => { - // 如果文件名和链接文件名相同,转换为 $link.sub 格式 - if (filename === linkFilename) { - return `$link.sub('${filename}')`; - } - // 如果不同,保持原格式(这种情况应该很少见) - return match; - }); - - // 修复 [filename](filename.md) 格式到 $link.sub('filename') 格式 - content = content.replace(/\[([^.\]]+)\]\(([^.\]]+)\.md\)/g, (match, displayText, linkFilename) => { - // 如果显示文本和链接文件名相同,转换为 $link.sub 格式 - if (displayText === linkFilename) { - return `$link.sub('${linkFilename}')`; - } - // 如果不同,保持原格式 - return match; - }); - - // 修复 [filename.md](filename.md) 格式(文件名包含点的情况) - content = content.replace(/\[([^]]+\.md)\]\(([^)]+\.md)\)/g, (match, displayText, linkFilename) => { - // 提取文件名(去掉.md扩展名) - const filename = displayText.replace('.md', ''); - const linkFile = linkFilename.replace('.md', ''); - - if (filename === linkFile) { - return `$link.sub('${filename}')`; - } - return match; - }); - - return content; -} - -// 处理单个文件 -function processFile(filePath) { - try { - const content = fs.readFileSync(filePath, 'utf8'); - const fixedContent = fixLinks(content); - - // 如果内容有变化,写回文件 - if (content !== fixedContent) { - fs.writeFileSync(filePath, fixedContent, 'utf8'); - console.log(`✓ Fixed links in: ${filePath}`); - return true; - } else { - console.log(`- No changes needed: ${filePath}`); - return false; - } - } catch (error) { - console.error(`✗ Error processing ${filePath}:`, error.message); - return false; - } -} - -// 递归处理目录 -function processDirectory(dirPath) { - try { - const items = fs.readdirSync(dirPath); - let fixedCount = 0; - let totalCount = 0; - - for (const item of items) { - const fullPath = path.join(dirPath, item); - const stat = fs.statSync(fullPath); - - if (stat.isDirectory()) { - // 递归处理子目录 - const result = processDirectory(fullPath); - fixedCount += result.fixed; - totalCount += result.total; - } else if (item.endsWith('.smd')) { - // 处理smd文件 - const fixed = processFile(fullPath); - if (fixed) fixedCount++; - totalCount++; - } - } - - return { fixed: fixedCount, total: totalCount }; - } catch (error) { - console.error(`✗ Error processing directory ${dirPath}:`, error.message); - return { fixed: 0, total: 0 }; - } -} - -// 主函数 -function main() { - console.log('🔗 Starting link format fix...'); - console.log(`📁 Source directory: ${config.sourceDir}`); - console.log(''); - - if (!fs.existsSync(config.sourceDir)) { - console.error(`✗ Source directory does not exist: ${config.sourceDir}`); - process.exit(1); - } - - const result = processDirectory(config.sourceDir); - - console.log(''); - console.log('📊 Fix Summary:'); - console.log(`✓ Fixed links in: ${result.fixed}/${result.total} files`); - - if (result.fixed > 0) { - console.log('🎉 Link fixes completed!'); - } else { - console.log('ℹ️ No link fixes needed'); - } -} - -// 运行脚本 -if (require.main === module) { - main(); -} - -module.exports = { - fixLinks, - processFile, - processDirectory -}; \ No newline at end of file diff --git a/fix_links_advanced.js b/fix_links_advanced.js deleted file mode 100644 index 4671401..0000000 --- a/fix_links_advanced.js +++ /dev/null @@ -1,155 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); - -// 配置 -const config = { - sourceDir: './content/learn' -}; - -// 修复链接格式 -function fixLinks(content) { - let changes = 0; - - // 1. 修复 [filename.md](filename.md) 格式到 $link.page('filename') 格式 - content = content.replace(/\[([^.\]]+)\.md\]\(([^.\]]+)\.md\)/g, (match, filename, linkFilename) => { - if (filename === linkFilename) { - changes++; - return `$link.page('${filename}')`; - } - return match; - }); - - // 2. 修复 [filename](filename.md) 格式到 $link.page('filename') 格式 - content = content.replace(/\[([^.\]]+)\]\(([^.\]]+)\.md\)/g, (match, displayText, linkFilename) => { - if (displayText === linkFilename) { - changes++; - return `$link.page('${linkFilename}')`; - } - return match; - }); - - // 3. 修复 [filename.md](filename.md) 格式(文件名包含点的情况) - content = content.replace(/\[([^]]+\.md)\]\(([^)]+\.md)\)/g, (match, displayText, linkFilename) => { - const filename = displayText.replace('.md', ''); - const linkFile = linkFilename.replace('.md', ''); - - if (filename === linkFile) { - changes++; - return `$link.page('${filename}')`; - } - return match; - }); - - // 4. 修复 [显示文本](filename.md) 格式到 [显示文本]($link.page('filename')) 格式 - content = content.replace(/\[([^]]+)\]\(([^.\]]+)\.md\)/g, (match, displayText, linkFilename) => { - if (displayText !== linkFilename) { - changes++; - return `[${displayText}]($link.page('${linkFilename}'))`; - } - return match; - }); - - // 5. 修复 [显示文本](filename.md) 格式(文件名包含点的情况) - content = content.replace(/\[([^]]+)\]\(([^)]+\.md)\)/g, (match, displayText, linkFilename) => { - const linkFile = linkFilename.replace('.md', ''); - if (displayText !== linkFile) { - changes++; - return `[${displayText}]($link.page('${linkFile}'))`; - } - return match; - }); - - return { content, changes }; -} - -// 处理单个文件 -function processFile(filePath) { - try { - const content = fs.readFileSync(filePath, 'utf8'); - const { content: fixedContent, changes } = fixLinks(content); - - if (changes > 0) { - fs.writeFileSync(filePath, fixedContent, 'utf8'); - console.log(`✓ Fixed ${changes} links in: ${filePath}`); - return { fixed: true, changes }; - } else { - console.log(`- No changes needed: ${filePath}`); - return { fixed: false, changes: 0 }; - } - } catch (error) { - console.error(`✗ Error processing ${filePath}:`, error.message); - return { fixed: false, changes: 0 }; - } -} - -// 递归处理目录 -function processDirectory(dirPath) { - try { - const items = fs.readdirSync(dirPath); - let fixedCount = 0; - let totalCount = 0; - let totalChanges = 0; - - for (const item of items) { - const fullPath = path.join(dirPath, item); - const stat = fs.statSync(fullPath); - - if (stat.isDirectory()) { - // 递归处理子目录 - const result = processDirectory(fullPath); - fixedCount += result.fixed; - totalCount += result.total; - totalChanges += result.totalChanges; - } else if (item.endsWith('.smd')) { - // 处理smd文件 - const result = processFile(fullPath); - if (result.fixed) fixedCount++; - totalCount++; - totalChanges += result.changes; - } - } - - return { fixed: fixedCount, total: totalCount, totalChanges }; - } catch (error) { - console.error(`✗ Error processing directory ${dirPath}:`, error.message); - return { fixed: 0, total: 0, totalChanges: 0 }; - } -} - -// 主函数 -function main() { - console.log('🔗 Starting advanced link format fix...'); - console.log(`📁 Source directory: ${config.sourceDir}`); - console.log(''); - - if (!fs.existsSync(config.sourceDir)) { - console.error(`✗ Source directory does not exist: ${config.sourceDir}`); - process.exit(1); - } - - const result = processDirectory(config.sourceDir); - - console.log(''); - console.log('📊 Fix Summary:'); - console.log(`✓ Fixed links in: ${result.fixed}/${result.total} files`); - console.log(`🔗 Total link changes: ${result.totalChanges}`); - - if (result.fixed > 0) { - console.log('🎉 Link fixes completed!'); - } else { - console.log('ℹ️ No link fixes needed'); - } -} - -// 运行脚本 -if (require.main === module) { - main(); -} - -module.exports = { - fixLinks, - processFile, - processDirectory -}; \ No newline at end of file diff --git a/remove_section_id_links.js b/remove_section_id_links.js deleted file mode 100644 index 0b7c9d8..0000000 --- a/remove_section_id_links.js +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); - -// 配置 -const config = { - sourceDir: './content/monthly' -}; - -// 转换链接格式:去掉 $section.id 只保留 Markdown 链接 -function removeSectionIdLinks(content) { - return content.replace(/\[\[(.+?)\]\((.+?)\)\]\(\$section\.id\((.+?)\)\)/g, '[$1]($2)'); -} - -// 处理单个文件 -function processFile(filePath) { - try { - const content = fs.readFileSync(filePath, 'utf8'); - const convertedContent = removeSectionIdLinks(content); - - // 如果内容有变化,写回文件 - if (content !== convertedContent) { - fs.writeFileSync(filePath, convertedContent, 'utf8'); - console.log(`✓ Removed section.id links in: ${filePath}`); - return true; - } else { - console.log(`- No changes needed: ${filePath}`); - return false; - } - } catch (error) { - console.error(`✗ Error processing ${filePath}:`, error.message); - return false; - } -} - -// 递归处理目录 -function processDirectory(dirPath) { - try { - const items = fs.readdirSync(dirPath); - let convertedCount = 0; - let totalCount = 0; - - for (const item of items) { - const fullPath = path.join(dirPath, item); - const stat = fs.statSync(fullPath); - - if (stat.isDirectory()) { - // 递归处理子目录 - const result = processDirectory(fullPath); - convertedCount += result.converted; - totalCount += result.total; - } else if (item.endsWith('.smd')) { - // 处理 smd 文件 - const converted = processFile(fullPath); - if (converted) convertedCount++; - totalCount++; - } - } - - return { converted: convertedCount, total: totalCount }; - } catch (error) { - console.error(`✗ Error processing directory ${dirPath}:`, error.message); - return { converted: 0, total: 0 }; - } -} - -// 主函数 -function main() { - console.log('🔄 Starting section.id link removal...'); - console.log(`📁 Source directory: ${config.sourceDir}`); - console.log('🔄 Removing section.id links'); - console.log(''); - - if (!fs.existsSync(config.sourceDir)) { - console.error(`✗ Source directory does not exist: ${config.sourceDir}`); - process.exit(1); - } - - const result = processDirectory(config.sourceDir); - - console.log(''); - console.log('📊 Removal Summary:'); - console.log(`✓ Removed section.id links in: ${result.converted}/${result.total} files`); - - if (result.converted > 0) { - console.log('🎉 Section.id link removal completed!'); - } else { - console.log('ℹ️ No section.id links found'); - } -} - -// 运行脚本 -if (require.main === module) { - main(); -} - -module.exports = { - removeSectionIdLinks, - processFile, - processDirectory -}; \ No newline at end of file From ff26ff1dda9ab26921bce9c8ec25f006c57a36b0 Mon Sep 17 00:00:00 2001 From: xihale Date: Sun, 6 Jul 2025 19:29:42 +0800 Subject: [PATCH 17/22] Remove obsolete TODO.md file and update contributing guidelines in SMD files to reflect new formatting standards. Change section titles for consistency and clarify local preview instructions using 'zine' instead of 'Hugo'. --- TODO.md | 3 --- content/community.smd | 2 +- content/contributing.smd | 20 ++++++++++++++------ content/post/2023-09-05-bog-gc-1.smd | 2 +- content/post/2023-09-05-hello-world.smd | 22 +++++++++++++--------- 5 files changed, 29 insertions(+), 20 deletions(-) delete mode 100644 TODO.md diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 4be3fe9..0000000 --- a/TODO.md +++ /dev/null @@ -1,3 +0,0 @@ -# 上游 Issue 计画 - -- 标题不能包含 `\|` (无报错,但是 `BUILD ERROR`) \ No newline at end of file diff --git a/content/community.smd b/content/community.smd index a1c7146..2a52e80 100644 --- a/content/community.smd +++ b/content/community.smd @@ -5,7 +5,7 @@ .layout = "index.shtml", .draft = false, --- -TODO: issue + [让我们一起探索 Zig 的魅力,推动 Zig 在中文社区内的发展!]($text.attrs('center','large','bold')) # [网站更新日志]($section.id('website-update-log')) diff --git a/content/contributing.smd b/content/contributing.smd index 3c958ed..e7a83af 100644 --- a/content/contributing.smd +++ b/content/contributing.smd @@ -12,22 +12,30 @@ Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文 2. 改进 zigcc 组织下的开源项目,这是 [open issues](https://github.com/search?q=state%3Aopen+org%3Azigcc++NOT+%E6%97%A5%E6%8A%A5&type=issues&ref=advsearch) 3. 参与不定期的线上会议 TODO -# [供稿方式]($section.id('供稿方式')) +# [供稿方式]($section.id('contribute')) 1. Fork 仓库 https://github.com/zigcc/zigcc.github.io -2. 在 `content/post` 内添加自己的文章(md 或 org 格式均可),文件命名为: `${YYYY}-${MM}-${DD}-${SLUG}.md` +2. 在 `content/post` 内添加自己的文章(smd格式),文件命名为: `${YYYY}-${MM}-${DD}-${SLUG}.smd` 3. 文件开始需要包含一些描述信息,例如: ``` --- -title: 欢迎 Zig 爱好者向本网站供稿 -author: 刘家财 -date: '2023-09-05T16:13:13+0800' +.title = "文章标题", +.date = @date("2025-06-05T16:00:00+0800"), +.author = "刘家财", +.layout = "post.shtml", +.draft = false, +.custom = { + .math = true, // 如果你要用到数学公式,请 设置 math 为 true; 否则可以忽略 + .mermaid = true, // 如果你要用到 mermaid 图表,请设置 mermaid 为 true; 否则可以忽略 +}, --- ``` # [本地预览]($section.id('本地预览')) -TODO + +首先你得安装 `zine`,安装方法见 [Quick Start](https://zine-ssg.io/quickstart/),或者你也可以使用包管理器安装(aur, ...) + ```bash zine ``` diff --git a/content/post/2023-09-05-bog-gc-1.smd b/content/post/2023-09-05-bog-gc-1.smd index c869763..8245624 100644 --- a/content/post/2023-09-05-bog-gc-1.smd +++ b/content/post/2023-09-05-bog-gc-1.smd @@ -5,7 +5,7 @@ .layout = "post.shtml", .draft = false, .custom = { - .math = true, // TODO: mathjax + .math = true, .mermaid = true, }, --- diff --git a/content/post/2023-09-05-hello-world.smd b/content/post/2023-09-05-hello-world.smd index 133b7d6..7df8808 100644 --- a/content/post/2023-09-05-hello-world.smd +++ b/content/post/2023-09-05-hello-world.smd @@ -11,22 +11,26 @@ - [ZigCC 网站](https://ziglang.cc) - [ZigCC 公众号](https://github.com/zigcc/.github/raw/main/zig_mp.png) -# [供稿方式]($section.id('供稿方式')) - -TODO +# [供稿方式]($section.id('contribute')) 1. Fork 仓库 https://github.com/zigcc/zigcc.github.io -2. ~~在 `content/post` 内添加自己的文章(md 或 org 格式均可),文件命名为: `${YYYY}-${MM}-${DD}-${SLUG}.md`~~ -3. ~~文件开始需要包含一些描述信息,例如[本文件](https://github.com/zigcc/zigcc.github.io/tree/main/content/post/2023-09-05-hello-world.md)中的:~~ +2. 在 `content/post` 内添加自己的文章(smd格式),文件命名为: `${YYYY}-${MM}-${DD}-${SLUG}.smd` +3. 文件开始需要包含一些描述信息,例如: ``` --- -title: 欢迎 Zig 爱好者向本网站供稿 -author: 刘家财 -date: '2023-09-05T16:13:13+0800' +.title = "文章标题", +.date = @date("2025-06-05T16:00:00+0800"), +.author = "刘家财", +.layout = "post.shtml", +.draft = false, +.custom = { + .math = true, // 如果你要用到数学公式,请 设置 math 为 true; 否则可以忽略 + .mermaid = true, // 如果你要用到 mermaid 图表,请设置 mermaid 为 true; 否则可以忽略 +}, --- ``` # [本地预览]($section.id('local-preview')) -在写完文章后,可以使用 [Hugo](https://gohugo.io/) 进行本地预览,只需在项目根目录执行 `hugo server`,这会启动一个 HTTP 服务,默认的访问地址是: http://localhost:1313/ +在写完文章后,可以使用 [zine](https://zine-ssg.io/) 进行本地预览,只需在项目根目录执行 `zine`,这会启动一个 HTTP 服务,默认的访问地址是: http://localhost:1990/ From bbd1f6871d66603a4d96fe3f9bd4d9ee97ce6e4f Mon Sep 17 00:00:00 2001 From: xihale Date: Sun, 6 Jul 2025 21:48:11 +0800 Subject: [PATCH 18/22] resolve change requests --- content/about.smd | 76 ++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/content/about.smd b/content/about.smd index 44bcdce..f56d4b1 100644 --- a/content/about.smd +++ b/content/about.smd @@ -1,56 +1,58 @@ --- -.title = "About", -.date = @date("1990-01-01T00:00:00"), +.date = "2024-08-04T08:51:07+0800", +.title = "学习 Zig", .author = "ZigCC", .layout = "index.shtml", .draft = false, ---- +--- -# [About Zine]($section.id('About Zine')) -Zine is an MIT-licensed project created by [Loris Cro](https://kristoff.it) and -other contributors listed on the [official repository](https://github.com/kristoff-it/zine/contributors). +# 资料 -Zine is inspired by [Hugo](https://gohugo.io) but features an entirely custom set -of authoring languages: +由于 Zig 目前还处于快速迭代,因此最权威的资料无疑是官方的 [Zig Language +Reference](https://ziglang.org/documentation/master/),遇到语言的细节问题,基本都可以在这里找到答案。 +其次是社区的一些高质量教程,例如: -- [Scripty](https://zine-ssg.io/docs/scripty/) is the small expression - language that both SuperHTML and SuperMD share to express templating logic. +[Zig Guide](https://zig.guide/) +英文资料, [Sobeston](https://github.com/Sobeston) 用户编写 -- [SuperHTML](https://zine-ssg.io/docs/superhtml/) is the HTML templating - language used by Zine. Unlike most `{{curly braced}}` templating languages, - SuperHTML uses valid HTML syntax to express the templating logic, adding only - minor extensions to normal HTML. - Thanks to this approach, it offers instant - syntax checking and autoformatting via [a CLI tool](https://github.com/kristoff-it/superhtml) as well as Language Server support ([VScode Extension](https://marketplace.visualstudio.com/items?itemName=LorisCro.super)). - - ># [NOTE]($block) - >The correct file extension for SuperHTML templates is `.shtml`. +[Zig in 30 minutes](https://gist.github.com/ityonemo/769532c2017ed9143f3571e5ac104e50) -- [SuperMD](https://zine-ssg.io/docs/supermd/) is a superset of Markdown - that, instead of relying on inline HTML, offers new constructs for expressing - content embeds without pulling into your content needless layouting concerns. - A CLI tool and language server for SuperMD is in the works. - - ># [NOTE]($block) - >The correct file extension for SuperMD pages is `.smd`. +[学习 Zig](https://ziglang.cc/learning-zig/) +该系列教程最初由 Karl Seguin +编写,该教程行文流畅,讲述的脉络由浅入深,深入浅出,是入门 Zig +非常不错的选择 -# [Zine is alpha software]($section.id('zine-is-alpha-software')) +[Zig 语言圣经](https://course.ziglang.cc) +一份内容全面、深入浅出介绍 Zig 的教程 -Zine is not yet complete. The main functionality is present and you will be able -to build even moderately complex static websites without issue. +[ziglings/exercises](https://codeberg.org/ziglings/exercises/) +Learn the Zig programming language by fixing tiny broken programs. -That said using Zine today does imply participating in the development process -to some degree, which usually means inquiring about the development status of -a feature you need, or reporting a bug. +[Zig Cookbook](https://cookbook.ziglang.cc/) +A collection of simple Zig programs that demonstrate good practices to +accomplish common programming tasks +[Awesome Zig](https://github.com/zigcc/awesome-zig) +A collection of some awesome public Zig programming language projects. -Here are some quicklinks related to Zine: +# 版本管理 -- Official Website: https://zine-ssg.io/ -- Source Code: https://github.com/kristoff-it/zine/ -- Discord: https://discord.com/invite/B73sGxF - +推荐使用版本管理工具 +[asdf](file:///post/2023/10/14/zig-version-manager/) 来安装 +Zig,具体步骤: +``` bash +git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0 +cat <<'EOF' >> $HOME/.bashrc +source "$HOME/.asdf/asdf.sh" +source "$HOME/.asdf/completions/asdf.bash" +EOF +asdf plugin-add zig https://github.com/zigcc/asdf-zig.git +# 安装最新版 +asdf install zig latest +asdf global zig latest +zig version +``` From 21e225fe4db72c8f683c62cdb11fa52d5f3fe251 Mon Sep 17 00:00:00 2001 From: xihale Date: Mon, 7 Jul 2025 10:43:01 +0800 Subject: [PATCH 19/22] Remove obsolete files including .cache_ggshield and _highlight.css. Update GitHub Actions workflow to support deployment from both 'zine' and 'main' branches. Enhance CSS styles for responsive navigation and improve layout templates with additional links. --- .cache_ggshield | 1 - .github/workflows/gh-pages.yml | 10 +- .gitignore | 10 ++ assets/_highlight.css | 265 --------------------------------- assets/style.css | 14 +- layouts/templates/base.shtml | 3 +- 6 files changed, 30 insertions(+), 273 deletions(-) delete mode 100644 .cache_ggshield create mode 100644 .gitignore delete mode 100644 assets/_highlight.css diff --git a/.cache_ggshield b/.cache_ggshield deleted file mode 100644 index ceb7666..0000000 --- a/.cache_ggshield +++ /dev/null @@ -1 +0,0 @@ -{"last_found_secrets": [{"match": "95eb7c12d136f77a6541bb255a8d9448a52bff447fb8216441841e4b7fd75a89", "name": "Generic High Entropy Secret - commit://d83fb6af5cb9407721ea97310b877c47b7702b74/content/post/news/index.smd"}]} \ No newline at end of file diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 15bee83..d4bd68b 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -1,9 +1,8 @@ name: Deploy the website to Github Pages on: - # Runs on pushes targeting the default branch push: - branches: ["zine"] + branches: ["zine", "main"] # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: @@ -26,7 +25,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-depth: 0 # Change if you need git info + fetch-depth: 0 # Change if you need git info - name: Setup Zine uses: kristoff-it/setup-zine@v1 @@ -42,8 +41,9 @@ jobs: - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - path: 'public' + path: "public" - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file + uses: actions/deploy-pages@v4 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd3340e --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.lock +.DS_Store +/public +/resources + +# zig +zig-cache/ +zig-out/ + +.cache_ggshield diff --git a/assets/_highlight.css b/assets/_highlight.css deleted file mode 100644 index b44c718..0000000 --- a/assets/_highlight.css +++ /dev/null @@ -1,265 +0,0 @@ -code .comment { - color: var(--comment-gray); -} - -code.html { -} - -code.html .string { - color: var(--dark-yellow); -} - -code.html .tag { - color: var(--blue); -} - -code.html .constant { - color: var(--light-red); -} - -code.html .attribute { - color: var(--light-yellow); -} - -code.html .punctuation { - color: var(--cyan); -} - -code.superhtml { -} - -code.superhtml .string.markup_link_url { - text-decoration: underline; - color: var(--blue); -} - -code.superhtml .string { - color: var(--dark-yellow); -} - -code.superhtml .tag { - color: var(--blue); -} - -code.superhtml .special { - color: var(--blue); -} - -code.superhtml .constant { - color: var(--light-red); -} - -code.superhtml .attribute { - color: var(--light-yellow); -} - -code.superhtml .punctuation { - color: var(--cyan); -} - -code.zig { -} - -code.zig .string { - color: var(--dark-yellow); -} - -code.zig .variable, -code.zig .field { - color: var(--light-yellow); -} - -code.zig .keyword.function { - color: var(--light-red); -} - -code.zig .bracket { - color: var(--cyan); -} - -code.zig .function { - color: var(--blue); -} - -code.zig .builtin { - color: var(--magenta); -} - -code.zig .operator, -code.zig .qualifier, -code.zig .attribute { - color: var(--light-red); -} - -code.ziggy { - color: var(--cyan); -} - -code.ziggy .keyword, -code.ziggy .type { - color: var(--light-yellow); -} - -code.ziggy .string { - color: var(--dark-yellow); -} - -code.ziggy .numeric.constant { - color: var(--magenta); -} - -code.ziggy .function { - color: var(--blue); -} - -code.ziggy-schema { - color: var(--cyan); -} - -code.ziggy-schema .keyword, -code.ziggy-schema .type { - color: var(--light-yellow); -} - -code.ziggy-schema .string { - color: var(--dark-yellow); -} - -code.ziggy-schema .numeric.constant { - color: var(--magenta); -} - -code.ziggy-schema .function { - color: var(--blue); -} - -code.markdown { - color: var(--code-fg); -} - -code.markdown .text.title { - color: var(--light-red); -} - -code.markdown .punctuation { - color: var(--light-red); -} - -code.toml { - color: var(--cyan); -} - -code.toml .property { - color: var(--light-yellow); -} - -code.toml .string { - color: var(--dark-yellow); -} - -code.toml .numeric.constant { - color: var(--magenta); -} - -code.toml .punctuation { - color: var(--blue); -} - -code.lua { -} - -code.lua .string { - color: var(--dark-yellow); -} - -code.lua .variable, -code.lua .field { - color: var(--light-yellow); -} - -code.lua .keyword.function { - color: var(--light-red); -} - -code.lua .bracket { - color: var(--cyan); -} - -code.lua .function { - color: var(--blue); -} - -code.lua .builtin { - color: var(--magenta); -} - -code.lua .operator, -code.lua .qualifier, -code.lua .attribute { - color: var(--light-red); -} - -code.bash { - display: block; /* Ensures it takes up full width for padding/margin */ - padding: 15px; - border-radius: 8px; - overflow-x: auto; /* Enable horizontal scrolling for long lines */ -} - -code.bash .function { - color: var(--blue); /* Using --blue for commands and functions */ -} - -code.bash .string { - color: var(--dark-yellow); /* Using --dark-yellow for strings */ -} - -code.bash .comment { - color: var(--comment-gray); /* Using --comment-gray for comments */ - font-style: italic; -} - -code.bash .operator, -code.bash .property { - color: var(--light-red); /* Using --light-red for operators and properties */ -} - -:root { - --light-yellow: #b58900; - --dark-yellow: #b57614; - --blue: #268bd2; - --cyan: #2aa198; - --light-red: #dc322f; - --dark-red: #cb4b16; - --comment-gray: #657b83; - --magenta: #6c71c4; -} -code.markdown { - color: #333; -} - -code, -pre { - background: var(--code-bg) !important; - color: var(--code-fg) !important; -} - -code.conf { - color: var(--cyan); -} - -code.conf .punctuation_bracket { - color: var(--blue); /* [section] */ - font-weight: bold; -} - -code.conf .function { - color: var(--light-yellow); /* key */ -} - -code.conf .comment { - color: var(--comment-gray); /* ; comment or # comment */ - font-style: italic; -} - -/* TODO: cpp support */ \ No newline at end of file diff --git a/assets/style.css b/assets/style.css index 8de7816..38b446f 100644 --- a/assets/style.css +++ b/assets/style.css @@ -84,7 +84,10 @@ li > code { .header { display: flex; justify-content: space-between; - align-items: center; + align-items: baseline; + @media screen and (max-width: 800px) { + justify-content: center; + } } .site-title { display: inline-block; @@ -127,6 +130,15 @@ nav a { text-decoration: none; font-size: large; margin: 0 10px; + @media screen and (max-width: 800px) { + margin: 0 8px; + } +} +nav a:first-child{ + margin-left: 0; +} +nav a:last-child{ + margin-right: 0; } footer > div { diff --git a/layouts/templates/base.shtml b/layouts/templates/base.shtml index a0c8674..84a1d41 100644 --- a/layouts/templates/base.shtml +++ b/layouts/templates/base.shtml @@ -13,11 +13,12 @@ From 0b67f728b1fcc91caa019a5c347efb02b1952aa9 Mon Sep 17 00:00:00 2001 From: xihale Date: Mon, 7 Jul 2025 11:33:46 +0800 Subject: [PATCH 20/22] Remove frpc.ini and README.org files, update zine.ziggy with the correct host URL, and delete .vscode/settings.json. This cleanup enhances project organization and ensures accurate site configuration. --- .vscode/settings.json | 6 ------ README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ README.org | 27 --------------------------- frpc.ini | 15 --------------- zine.ziggy | 2 +- 5 files changed, 43 insertions(+), 49 deletions(-) delete mode 100644 .vscode/settings.json create mode 100644 README.md delete mode 100644 README.org delete mode 100644 frpc.ini diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d2acfc4..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "cSpell.words": [ - "stylesheet", - "subpages" - ] -} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3702eb5 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +[![](https://github.com/zigcc/zigcc.github.io/actions/workflows/gh-pages.yml/badge.svg)](https://github.com/zigcc/zigcc.github.io/actions/workflows/gh-pages.yml) + +# Zig 语言中文社区 + +- +- 如果没有特殊声明,本网站内容均采用 [CC BY-NC-ND + 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/) 协议 + +
    +

    欢迎来到 Zig 语言中文社区!(Zig Chinese Community,简称:ZigCC)

    + + + + + + + + + +
    + +> Zig Chinese Community is dedicated to sharing and spreading the use of +> Zig language among Chinese users. + +本网站使用 [zine](https://zine-ssg.io/) 进行构建。 + +参考 [quickstart](https://zine-ssg.io/quickstart/) 进行安装: + +``` bash +# aur +yay -S zine + +# zig build install +git clone https://github.com/kristoff-it/zine.git +cd zine +zig build install --release=safe +``` + +``` bash +# run server +zine +``` \ No newline at end of file diff --git a/README.org b/README.org deleted file mode 100644 index 0417ffd..0000000 --- a/README.org +++ /dev/null @@ -1,27 +0,0 @@ -[[https://github.com/zigcc/zigcc.github.io/actions/workflows/gh-pages.yml][https://github.com/zigcc/zigcc.github.io/actions/workflows/gh-pages.yml/badge.svg]] - -* Zig 语言中文社区 -- [[https://ziglang.cc]] -- 如果没有特殊声明,本网站内容均采用 [[https://creativecommons.org/licenses/by-nc-nd/4.0/][CC BY-NC-ND 4.0]] 协议 - -#+BEGIN_EXPORT html -
    -

    欢迎来到 Zig 语言中文社区!(Zig Chinese Community,简称:ZigCC)

    - - - - - - - - - -
    -#+END_EXPORT - - -#+begin_quote -Zig Chinese Community is dedicated to sharing and spreading the use of Zig language among Chinese users. -#+end_quote - -TODO: Contribute docs \ No newline at end of file diff --git a/frpc.ini b/frpc.ini deleted file mode 100644 index 12485d8..0000000 --- a/frpc.ini +++ /dev/null @@ -1,15 +0,0 @@ -[common] -user = howijs6zqne9fcvt - -sakura_mode = true -login_fail_exit = false - -server_addr = frp-cup.com -server_port = 8088 - -[xtest] -# id = 21713037 -type = http -local_ip = 127.0.0.1 -local_port = 1990 -custom_domains = frp.xihale.top diff --git a/zine.ziggy b/zine.ziggy index bcf07f9..1fe8372 100644 --- a/zine.ziggy +++ b/zine.ziggy @@ -1,6 +1,6 @@ Site { .title = "Zig 语言中文社区", - .host_url = "https://example.com",// TODO + .host_url = "https://ziglang.cc", .content_dir_path = "content", .layouts_dir_path = "layouts", .assets_dir_path = "assets", From cf98dab90bf1cc47908e5dc98ff2c0adad92d0fc Mon Sep 17 00:00:00 2001 From: jiacai2050 Date: Sun, 13 Jul 2025 12:16:40 +0800 Subject: [PATCH 21/22] trivial fix --- README.md | 18 +++------------- content/about.smd | 36 +++++++------------------------- content/contributing.smd | 7 +++---- content/learn/installing-zig.smd | 2 +- content/monthly/202406.smd | 9 ++++---- 5 files changed, 19 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 3702eb5..4e84cba 100644 --- a/README.md +++ b/README.md @@ -22,21 +22,9 @@ > Zig Chinese Community is dedicated to sharing and spreading the use of > Zig language among Chinese users. -本网站使用 [zine](https://zine-ssg.io/) 进行构建。 - -参考 [quickstart](https://zine-ssg.io/quickstart/) 进行安装: - -``` bash -# aur -yay -S zine - -# zig build install -git clone https://github.com/kristoff-it/zine.git -cd zine -zig build install --release=safe -``` +本网站使用 [zine](https://zine-ssg.io/) 进行构建,可参考 [quickstart](https://zine-ssg.io/quickstart/) 进行安装,网站预览命令: ``` bash -# run server +# Serve the site zine -``` \ No newline at end of file +``` diff --git a/content/about.smd b/content/about.smd index f56d4b1..773dbbc 100644 --- a/content/about.smd +++ b/content/about.smd @@ -12,47 +12,25 @@ Reference](https://ziglang.org/documentation/master/),遇到语言的细节问题,基本都可以在这里找到答案。 其次是社区的一些高质量教程,例如: -[Zig Guide](https://zig.guide/) +[Zig Guide](https://zig.guide/) 英文资料, [Sobeston](https://github.com/Sobeston) 用户编写 -[Zig in 30 minutes](https://gist.github.com/ityonemo/769532c2017ed9143f3571e5ac104e50) +[Zig in 30 minutes](https://gist.github.com/ityonemo/769532c2017ed9143f3571e5ac104e50) -[学习 Zig](https://ziglang.cc/learning-zig/) +[学习 Zig](https://ziglang.cc/learning-zig/) 该系列教程最初由 Karl Seguin 编写,该教程行文流畅,讲述的脉络由浅入深,深入浅出,是入门 Zig 非常不错的选择 -[Zig 语言圣经](https://course.ziglang.cc) +[Zig 语言圣经](https://course.ziglang.cc) 一份内容全面、深入浅出介绍 Zig 的教程 -[ziglings/exercises](https://codeberg.org/ziglings/exercises/) +[ziglings/exercises](https://codeberg.org/ziglings/exercises/) Learn the Zig programming language by fixing tiny broken programs. -[Zig Cookbook](https://cookbook.ziglang.cc/) +[Zig Cookbook](https://cookbook.ziglang.cc/) A collection of simple Zig programs that demonstrate good practices to accomplish common programming tasks -[Awesome Zig](https://github.com/zigcc/awesome-zig) +[Awesome Zig](https://github.com/zigcc/awesome-zig) A collection of some awesome public Zig programming language projects. - -# 版本管理 - -推荐使用版本管理工具 -[asdf](file:///post/2023/10/14/zig-version-manager/) 来安装 -Zig,具体步骤: - -``` bash -git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0 -cat <<'EOF' >> $HOME/.bashrc -source "$HOME/.asdf/asdf.sh" -source "$HOME/.asdf/completions/asdf.bash" -EOF - -asdf plugin-add zig https://github.com/zigcc/asdf-zig.git - -# 安装最新版 -asdf install zig latest -asdf global zig latest -zig version -``` - diff --git a/content/contributing.smd b/content/contributing.smd index e7a83af..7bd79fb 100644 --- a/content/contributing.smd +++ b/content/contributing.smd @@ -4,13 +4,12 @@ .author = "ZigCC", .layout = "index.shtml", .draft = false, ---- +--- Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文群体中的使用,有多种方式可以参与进来: 1. 供稿,分享自己使用 Zig 的心得,方式见下文 -2. 改进 zigcc 组织下的开源项目,这是 [open issues](https://github.com/search?q=state%3Aopen+org%3Azigcc++NOT+%E6%97%A5%E6%8A%A5&type=issues&ref=advsearch) -3. 参与不定期的线上会议 TODO +2. 改进 zigcc 组织下的开源项目,这是 [open issues](https://ask.ziglang.cc/github) # [供稿方式]($section.id('contribute')) @@ -34,7 +33,7 @@ Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文 # [本地预览]($section.id('本地预览')) -首先你得安装 `zine`,安装方法见 [Quick Start](https://zine-ssg.io/quickstart/),或者你也可以使用包管理器安装(aur, ...) +首先你得安装 `zine`,安装方法见 [Quick Start](https://zine-ssg.io/quickstart/),之后就可以通过执行 `zine` 命令来预览你的文章。 ```bash zine diff --git a/content/learn/installing-zig.smd b/content/learn/installing-zig.smd index db8736b..f048232 100644 --- a/content/learn/installing-zig.smd +++ b/content/learn/installing-zig.smd @@ -21,7 +21,7 @@ brew install asdf asdf plugin add zig https://github.com/zigcc/asdf-zig.git -# [安装最新版]($section.id('install-latest-version')) +# 安装最新稳定版 asdf install zig latest # 设置为全局版本 diff --git a/content/monthly/202406.smd b/content/monthly/202406.smd index 373b107..0447060 100644 --- a/content/monthly/202406.smd +++ b/content/monthly/202406.smd @@ -35,7 +35,7 @@ const map = std.StaticStringMap(T).initComptime(kvs_list); 老朋友 openmymind 的又一篇好文章:如何利用 Zig 的 Allocator 来实现请求级别的内存分配。 Zig Allocator -的最佳应用。[这里](file:///post/2024/06/16/leveraging-zig-allocator/)它的中文翻译。 +的最佳应用。[这里](/post/2024-06-16-leveraging-zig-allocator/)它的中文翻译。 ``` zig const FallbackAllocator = struct { @@ -98,6 +98,7 @@ fn run(worker: *Worker) void { worker.write(conn.res); } } +``` # [On Zig vs Rust at work and the choice we made](https://ludwigabap.bearblog.dev/zig-vs-rust-at-work-the-choice-we-made/) @@ -158,11 +159,11 @@ News](https://news.ycombinator.com/item?id=40681862) # [项目/工具]($section.id('projects-tools')) -[malcolmstill/zware](https://github.com/malcolmstill/zware) +[malcolmstill/zware](https://github.com/malcolmstill/zware) Zig WebAssembly Runtime Engine -[Cloudef/zig-aio](https://github.com/Cloudef/zig-aio) -iouring like asynchronous API and coroutine powered IO tasks +[Cloudef/zig-aio](https://github.com/Cloudef/zig-aio) +io_uring like asynchronous API and coroutine powered IO tasks for zig # [Zig 语言更新](https://github.com/ziglang/zig/pulls?page=1&q=+is%3Aclosed+is%3Apr+closed%3A2024-06-01..2024-07-01) From a548bb908cc51c80bb982a9f56f75dd74a24629c Mon Sep 17 00:00:00 2001 From: jiacai2050 Date: Sun, 13 Jul 2025 12:19:35 +0800 Subject: [PATCH 22/22] add mirror workflow --- .github/workflows/gh-pages.yml | 8 +++++--- .github/workflows/mirror.yml | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/mirror.yml diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index d4bd68b..94fa5ad 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -10,11 +10,14 @@ permissions: pages: write id-token: write +defaults: + run: + shell: bash + # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: "pages" - cancel-in-progress: false + cancel-in-progress: true jobs: deploy: @@ -46,4 +49,3 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 - diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml new file mode 100644 index 0000000..97f861b --- /dev/null +++ b/.github/workflows/mirror.yml @@ -0,0 +1,18 @@ +name: Mirror + +on: + push: + branches: + - main** + workflow_dispatch: + +jobs: + codeberg: + if: github.repository_owner == 'zigcc' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - run: | + git push --tags --force https://${{ secrets.CBTOKEN }}@codeberg.org/jiacai2050/zigcc.git "refs/remotes/origin/*:refs/heads/*"