<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Ruby China 社区 Go 节点</title>
    <link>https://ruby-china.org/</link>
    <description>Ruby China 社区 Go 节点最新发帖。</description>
    <item>
      <title>2025 (非预制) Vue 3 + Golang 全栈课 - 第一讲</title>
      <description>&lt;p&gt;今天开始，录制一个全栈入门课，讲解 Vue 3 + Golang 前后端分离开发。&lt;/p&gt;

&lt;p&gt;视频地址： [ 2025 (非预制) Vue 3 + Golang 全栈课]
&lt;span class="embed-responsive embed-responsive-16by9"&gt;&lt;iframe class="embed-responsive-item" src="//player.bilibili.com/player.html?bvid=1ByWGz5EtZ" allowfullscreen=""&gt;&lt;/iframe&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;欢迎感兴趣的观看一下，如果觉得有价值，欢迎点赞收藏分享。&lt;/p&gt;</description>
      <author>daqing</author>
      <pubDate>Sat, 20 Sep 2025 10:44:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/44320</link>
      <guid>https://ruby-china.org/topics/44320</guid>
    </item>
    <item>
      <title>一个 OSS 的新的 Golang SDK</title>
      <description>&lt;p&gt;实话说，这是本人写的第一个 golang 项目，之前写过 rust 的 aliyun oss 的 sdk，获得了 40 多个 star，因为最近在学习 go 语言，就顺手写了一下这个项目&lt;/p&gt;

&lt;p&gt;让人感到意外的是，这个 library 跟 aliyun 官方提供的 sdk 效率提高了近一倍&lt;/p&gt;

&lt;p&gt;附项目地址： &lt;a href="https://github.com/tu6ge/oss-go" rel="nofollow" target="_blank"&gt;https://github.com/tu6ge/oss-go&lt;/a&gt;&lt;/p&gt;</description>
      <author>tu6ge</author>
      <pubDate>Mon, 14 Apr 2025 15:37:11 +0800</pubDate>
      <link>https://ruby-china.org/topics/44127</link>
      <guid>https://ruby-china.org/topics/44127</guid>
    </item>
    <item>
      <title>Lazyapi 通过终端管理你的 API，尽可能减少双手离开键盘的次数😂</title>
      <description>&lt;h2 id="Lazyapi"&gt;Lazyapi&lt;/h2&gt;&lt;h2 id="是什么？"&gt;是什么？&lt;/h2&gt;
&lt;p&gt;通过终端操作的 API 管理工具，该项目受到&lt;a href="https://github.com/jesseduffield/lazygit" rel="nofollow" target="_blank" title=""&gt;lazygit&lt;/a&gt;的鼓舞与启发，在某些简单的 API 对接和调用的场景上，希望能简单，快速，直观的处理 API，包含：对 API 的增删改查，请求，历史请求记录等操作。&lt;/p&gt;
&lt;h2 id="示例"&gt;示例&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/df0542f3-fe70-4173-9520-5a7bd5bcc285.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/27306c1a-f1e7-4771-9ed4-f4742b17dbde.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/b2a48846-c810-4330-bb0c-90d9b939a3df.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="使用"&gt;使用&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/GengCen-Qin/lazyapi" rel="nofollow" target="_blank" title=""&gt;Github&lt;/a&gt; 下载项目到本地，并执行 &lt;code&gt;go build&lt;/code&gt; 生成可执行文件&lt;/p&gt;

&lt;p&gt;对于数据的存储与读取，通过 Sqlite，Mac 用户会自动尝试在 &lt;code&gt;~/Library/Application Support/lazyapi&lt;/code&gt; 下生成存储文件&lt;/p&gt;

&lt;p&gt;在操作上，你可以选择先创建一个 API，然后对其进行请求发送，目前仅支持 GET/POST 请求，请求参数要求为 json 格式，如果是 POST 请求会放到 body 中，如果是 Get 请求，会拼接在 url 后面，后续会增加更细致的功能。&lt;/p&gt;

&lt;p&gt;某些临时请求上，可以直接通过快捷键&lt;code&gt;g&lt;/code&gt;快速生成一个 Get 请求，&lt;code&gt;p&lt;/code&gt;快速生成一个 Post 请求，当回车后会直接发送请求&lt;/p&gt;
&lt;h2 id="注意"&gt;注意&lt;/h2&gt;
&lt;p&gt;由于底层使用的&lt;a href="https://github.com/jroimartin/gocui" rel="nofollow" target="_blank" title=""&gt;gocui&lt;/a&gt;对中文支持的不好，所以当中文输入处理上会有问题，目前仅 fork 了一个版本，解决了输入和展示的问题，但是在光标移动和删除上，仍有问题，需要操作两次才行，例如删除时，删除键按两次，因为底层存储时中文字长占了两位。&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sun, 30 Mar 2025 17:14:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/44112</link>
      <guid>https://ruby-china.org/topics/44112</guid>
    </item>
    <item>
      <title>欢迎观看我今天录制的课程：Golang 2024 实战教程 - 让 Claude AI 帮你写代码</title>
      <description>&lt;p&gt;大家好，我今天录制了 Golang 实战教程：&lt;/p&gt;

&lt;p&gt;Golang 2024 实战教程 - 让 Claude AI 帮你写代码
&lt;span class="embed-responsive embed-responsive-16by9"&gt;&lt;iframe class="embed-responsive-item" src="//player.bilibili.com/player.html?bvid=16m4me8Ef7" allowfullscreen=""&gt;&lt;/iframe&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;欢迎来 B 站观看，一键三连&lt;/p&gt;</description>
      <author>daqing</author>
      <pubDate>Sun, 15 Sep 2024 16:41:56 +0800</pubDate>
      <link>https://ruby-china.org/topics/43887</link>
      <guid>https://ruby-china.org/topics/43887</guid>
    </item>
    <item>
      <title>Go 语言在国内发展的不行了吗？怎么没有看到像样的中文官方社区？</title>
      <description>&lt;p&gt;搜了一下 Go 中文社区，发现没有官方论坛（就是类似咱们 Ruby China 这样的）&lt;/p&gt;

&lt;p&gt;怎么回事？&lt;/p&gt;</description>
      <author>daqing</author>
      <pubDate>Thu, 18 Jul 2024 13:13:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/43814</link>
      <guid>https://ruby-china.org/topics/43814</guid>
    </item>
    <item>
      <title>Rust2go: calls Go from Rust </title>
      <description>&lt;h2 id="Introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Recently, I've been developing an experimental project called &lt;a href="https://github.com/yfractal/ccache" rel="nofollow" target="_blank" title=""&gt;ccache&lt;/a&gt;, which is a Redis client-side caching that guarantees consistency. Since it operates on the client side, I need to ensure it supports different programming languages.&lt;/p&gt;

&lt;p&gt;The common practice is to write similar logic in different languages, as seen with Redis client and OpenTelemetry instrument library. However, this approach involves tedious and repetitive work.&lt;/p&gt;

&lt;p&gt;One potential solution is to write the core functionality in Rust and integrate it with different languages. To achieve this, we need to address the discrepancies between Rust and other languages, such as how to represent data and manage memory safely.&lt;/p&gt;

&lt;p&gt;Rust2go is a practical FFI  framework that enables calling Go from Rust. In this article, I will introduce how it works.&lt;/p&gt;
&lt;h2 id="Benefits"&gt;Benefits&lt;/h2&gt;
&lt;p&gt;Due to its low overhead and safety guarantees, Rust has been integrated into many other systems traditionally written in C, such as Ruby and Linux.&lt;/p&gt;

&lt;p&gt;Integrating Rust with other high-level languages is beneficial as it can improve performance and reduce repetitive work.&lt;/p&gt;

&lt;p&gt;For example, ByteDance reduced CPU usage by more than 30% after migrating a core service from Golang to Rust[2].&lt;/p&gt;

&lt;p&gt;Additionally, OpenTelemetry supports 11 languages[3]. Using Rust for core functionality can significantly reduce development efforts and prevent inconsistencies between different language implementations.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://west-barber-f19.notion.site/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3ad64ea3-ffc7-46f7-a5e8-78e7a3534dae%2Fc3ecbef2-8cb2-4bae-bf1d-7744801cf570%2FUntitled.png?table=block&amp;amp;id=8150861d-5437-4eb8-9d6a-2c6373196403&amp;amp;spaceId=3ad64ea3-ffc7-46f7-a5e8-78e7a3534dae&amp;amp;width=1420&amp;amp;userId=&amp;amp;cache=v2" title="" alt="Untitled"&gt;&lt;/p&gt;
&lt;h2 id="How Rust2go Works"&gt;How Rust2go Works&lt;/h2&gt;&lt;h2 id="Calling Go Functions"&gt;Calling Go Functions&lt;/h2&gt;
&lt;p&gt;After building the Go code into a library and linking it to Rust, the Go functions become accessible within the Rust project.&lt;/p&gt;

&lt;p&gt;However, Rust and Go have different calling conventions, so Rust cannot directly call Go functions. One solution is to use a trampoline to handle this issue[4]. Due to the unstable Rust ABI and the desire to address goroutine stack expansion, the author of rust2go chose not to use this method.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ihciah/rust2go?tab=readme-ov-file" rel="nofollow" target="_blank" title=""&gt;rust2go&lt;/a&gt; uses the C ABI as a "bridge" between Rust and Go. The Go functions are exposed as C functions through cgo, and Rust calls these C functions.&lt;/p&gt;
&lt;h2 id="Memory Representation"&gt;Memory Representation&lt;/h2&gt;
&lt;p&gt;Rust and Go represent structs in different ways. In Rust2go, a struct is first converted to a C struct and then to a Go struct. For example, a Rust struct &lt;code&gt;DemoUser&lt;/code&gt; is converted to &lt;code&gt;DemoUserRef&lt;/code&gt; and then to a Go &lt;code&gt;DemoUser&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;DemoUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;DemoUserRef&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;StringRef&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;DemoUserRef&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;newDemoUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DemoUserRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;DemoUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DemoUser&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;newString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;newC_uint8_t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then it coverts the primate types, for example, &lt;code&gt;StringRef&lt;/code&gt; is converted to Go string by &lt;code&gt;newString&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;newString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s_ref&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;unsafeString&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;unsafe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s_ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s_ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;unsafeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sliceHeader&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SliceHeader&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unsafe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="n"&gt;Len&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Cap&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;unsafe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sliceHeader&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I will explain why Rust2go uses &lt;code&gt;XXXRef&lt;/code&gt; in the next section.&lt;/p&gt;
&lt;h2 id="Passing Variables Between Rust and Go"&gt;Passing Variables Between Rust and Go&lt;/h2&gt;
&lt;p&gt;In the previous section, I explained how rust2go understands structs in Rust and Go. Now, I will explain how it passes variables between the two languages.&lt;/p&gt;
&lt;h2 id="Passing Arguments to Go"&gt;Passing Arguments to Go&lt;/h2&gt;
&lt;p&gt;The most simple and straightforward method is to use serialization protocols like Thrift and Protocol Buffers. Rust2go does not choose this method as it wastes CPU time converting the data back and forth.&lt;/p&gt;

&lt;p&gt;Instead, it passes arguments through pointers and converts the data to make it understandable for Rust and Go. This avoids deep copying, such as strings and binary data.&lt;/p&gt;

&lt;p&gt;This method adheres to Rust's safety rules because the arguments are "borrowed" by Go, and the memory is "owned" by Rust. Once Go finishes using the data, it frees its allocated memory, but the variables' memory allocated by Rust is not freed by Go.&lt;/p&gt;
&lt;h2 id="Receiving Return Variables from Go"&gt;Receiving Return Variables from Go&lt;/h2&gt;
&lt;p&gt;The return variables are created by Go, so Go can free them when necessary. Rust calling Rust does not have this problem because the variable can own the return result, such as &lt;code&gt;let x = some_func();&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Rust2go handles this by copying the variable in the C callback so that Rust and Go can manage the "same" variable independently.&lt;/p&gt;
&lt;h2 id="Summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;This article provides an introduction to how rust2go works. For more details, please refer to the author's article[2].&lt;/p&gt;
&lt;h2 id="References"&gt;References&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/ihciah/rust2go?tab=readme-ov-file" rel="nofollow" target="_blank" title=""&gt;https://github.com/ihciah/rust2go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.ihcblog.com/rust2go/" rel="nofollow" target="_blank"&gt;https://en.ihcblog.com/rust2go/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/status/" rel="nofollow" target="_blank"&gt;https://opentelemetry.io/status/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://metalbear.co/blog/hooking-go-from-rust-hitchhikers-guide-to-the-go-laxy/" rel="nofollow" target="_blank"&gt;https://metalbear.co/blog/hooking-go-from-rust-hitchhikers-guide-to-the-go-laxy/&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>yfractal</author>
      <pubDate>Mon, 24 Jun 2024 21:23:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/43765</link>
      <guid>https://ruby-china.org/topics/43765</guid>
    </item>
    <item>
      <title>CRUD 终结者：介绍 Airway 框架的代码生成器</title>
      <description>&lt;p&gt;不到 1 分钟，自动生成 CRUD 代码。&lt;/p&gt;

&lt;p&gt;视频介绍：&lt;a href="https://www.bilibili.com/video/BV1k64y1L7eq" rel="nofollow" target="_blank"&gt;https://www.bilibili.com/video/BV1k64y1L7eq&lt;/a&gt;&lt;/p&gt;</description>
      <author>daqing</author>
      <pubDate>Sun, 03 Dec 2023 13:32:57 +0800</pubDate>
      <link>https://ruby-china.org/topics/43494</link>
      <guid>https://ruby-china.org/topics/43494</guid>
    </item>
    <item>
      <title>开源 Go 开发框架：Airway - 视频介绍（Golang）</title>
      <description>&lt;p&gt;今天为我开发的 Go 开源框架，录制了一个视频介绍，欢迎感兴趣的朋友查看。&lt;/p&gt;

&lt;p&gt;开源仓库地址：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/daqing/airway" rel="nofollow" target="_blank"&gt;https://github.com/daqing/airway&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;视频地址：
&lt;span class="embed-responsive embed-responsive-16by9"&gt;&lt;iframe class="embed-responsive-item" src="//player.bilibili.com/player.html?bvid=1Pu4y1j7TA" allowfullscreen=""&gt;&lt;/iframe&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <author>daqing</author>
      <pubDate>Tue, 21 Nov 2023 00:03:01 +0800</pubDate>
      <link>https://ruby-china.org/topics/43479</link>
      <guid>https://ruby-china.org/topics/43479</guid>
    </item>
    <item>
      <title>Airway - 全栈 Go 开源 Web 框架，开始写文档了</title>
      <description>&lt;p&gt;之前也在论坛里发过贴，有朋友说缺少文档。&lt;/p&gt;

&lt;p&gt;最近把项目又完善了一些，今天开始陆续写中文文档了。&lt;/p&gt;

&lt;p&gt;项目地址：&lt;a href="https://github.com/daqing/airway" rel="nofollow" target="_blank"&gt;https://github.com/daqing/airway&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;中文文档：&lt;a href="https://github.com/daqing/airway/blob/main/docs/zh-CN/README.md" rel="nofollow" target="_blank"&gt;https://github.com/daqing/airway/blob/main/docs/zh-CN/README.md&lt;/a&gt;&lt;/p&gt;</description>
      <author>daqing</author>
      <pubDate>Mon, 30 Oct 2023 16:04:57 +0800</pubDate>
      <link>https://ruby-china.org/topics/43431</link>
      <guid>https://ruby-china.org/topics/43431</guid>
    </item>
    <item>
      <title>继续分享我的 Go 框架，支持插件结构，目前已经内置用户注册、标签、网站设置、帖子、节点、回复、签到、点赞/关注/收藏 等多个 API 插件</title>
      <description>&lt;p&gt;项目地址：&lt;a href="https://github.com/daqing/airway/" rel="nofollow" target="_blank"&gt;https://github.com/daqing/airway/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;欢迎加星，交流。&lt;/p&gt;</description>
      <author>daqing</author>
      <pubDate>Fri, 20 Oct 2023 20:19:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/43409</link>
      <guid>https://ruby-china.org/topics/43409</guid>
    </item>
    <item>
      <title>分享我设计的 Go 语言 API 框架：Airway</title>
      <description>&lt;p&gt;先上地址：&lt;a href="https://github.com/daqing/airway" rel="nofollow" target="_blank"&gt;https://github.com/daqing/airway&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;思路主要是受 Ruby on Rails 框架启发，你可以发现，目录结构都是非常相似的。&lt;/p&gt;

&lt;p&gt;这个项目的代码，是从我近期开发的后端代码提取出来的。&lt;/p&gt;

&lt;p&gt;求 star ~~~&lt;/p&gt;</description>
      <author>daqing</author>
      <pubDate>Sat, 16 Sep 2023 17:54:20 +0800</pubDate>
      <link>https://ruby-china.org/topics/43335</link>
      <guid>https://ruby-china.org/topics/43335</guid>
    </item>
    <item>
      <title>介绍我用 Go 开发的小工具：rename</title>
      <description>&lt;h2 id="主打功能"&gt;主打功能&lt;/h2&gt;
&lt;p&gt;重命名文件。（其实，也就这一个功能）&lt;/p&gt;
&lt;h2 id="安装方式"&gt;安装方式&lt;/h2&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/daqing/rename@latest
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="用法"&gt;用法&lt;/h2&gt;
&lt;p&gt;a. 把 &lt;code&gt;good.txt&lt;/code&gt; 重名为 &lt;code&gt;bad.txt&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rename good.txt bad
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;b. 把 &lt;code&gt;good.markdown&lt;/code&gt; 重命名为 &lt;code&gt;good.md&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rename good.markdown .md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;c. 把 &lt;code&gt;/var/log/&lt;/code&gt; 目录下的 日志文件，批量修改为 &lt;code&gt;.backup&lt;/code&gt; 后缀：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;find /var/log &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.log'&lt;/span&gt; | xargs &lt;span class="nt"&gt;-I&lt;/span&gt;&lt;span class="o"&gt;{}&lt;/span&gt; rename &lt;span class="o"&gt;{}&lt;/span&gt; .backup
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="开源地址"&gt;开源地址&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/daqing/rename" rel="nofollow" target="_blank"&gt;https://github.com/daqing/rename&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;求 Star :-)&lt;/p&gt;</description>
      <author>daqing</author>
      <pubDate>Fri, 15 Sep 2023 13:08:46 +0800</pubDate>
      <link>https://ruby-china.org/topics/43330</link>
      <guid>https://ruby-china.org/topics/43330</guid>
    </item>
    <item>
      <title>Go 的内建函数 append 为什么会返回一个新的 slice？</title>
      <description>&lt;p&gt;Go 源码中对于 slice 的声明是这样的：&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;slice&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="n"&gt;unsafe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;
    &lt;span class="nb"&gt;len&lt;/span&gt;   &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="nb"&gt;cap&lt;/span&gt;   &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并且切片还是一个指针，所以可以认为 &lt;code&gt;s := make([]string, 10)&lt;/code&gt; 约等于 &lt;code&gt;s := &amp;amp;slice{}&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;那 &lt;code&gt;append&lt;/code&gt; 方法既然接收 &lt;code&gt;*slice&lt;/code&gt; 作为参数，为什么还会返回一个新的 slice 呢？它完全可以修改当前的 slice 呀，再创建一个 &lt;code&gt;*slice&lt;/code&gt; 返回不是会产生更多的内存申请和释放吗？&lt;/p&gt;</description>
      <author>willx</author>
      <pubDate>Wed, 01 Mar 2023 21:18:33 +0800</pubDate>
      <link>https://ruby-china.org/topics/42917</link>
      <guid>https://ruby-china.org/topics/42917</guid>
    </item>
    <item>
      <title>凹语言 v0.2.1 支持浏览器环境构建</title>
      <description>&lt;p&gt;凹语言仓库：&lt;a href="https://github.com/wa-lang/wa" rel="nofollow" target="_blank"&gt;https://github.com/wa-lang/wa&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;凹语言最新发布了 &lt;a href="https://github.com/wa-lang/wa/releases/tag/v0.2.1" rel="nofollow" target="_blank" title=""&gt;v0.2.1&lt;/a&gt; 版本，经过小伙伴们的通力合作，终于可以在浏览器环境编译并执行（不依赖后台服务）。Playground 在线地址 &lt;a href="https://wa-lang.org/playground" rel="nofollow" target="_blank" title=""&gt;https://wa-lang.org/playground&lt;/a&gt;，以下是执行效果：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://wa-lang.org/smalltalk/images/st0011-01.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;本地安装最新的 凹语言™ v0.2.1 版本：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ go install github.com/wa-lang/wa@v0.2.1
go: downloading github.com/wa-lang/wa v0.2.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;命令行新增加了打印文本 logo 的命令：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ wa logo -more

+---+    +---+
| o |    | o |
|   +----+   |
|            |
|     Wa     |
|            |
+------------+

+---+    +---+
| * |    | * |
|   +----+   |
|            |
|    \/\/    |
|            |
+------------+

...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本地环境支持以被嵌入宿主脚本模式执行：&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/wa-lang/wa/api"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello.wa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"fn main() { println(40+2) }"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同时命令行修复了格式化的问题，以下是格式化效果：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat hello.wa
   # 版权 @2019 凹语言 作者。保留所有权利。

fn main() {
println( add(40 , 2) )
}

fn add(a:i32,b:i32)=&amp;gt; i32 {
return a+b
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 &lt;code&gt;wa fmt hello.wa&lt;/code&gt; 命令格式化，效果如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat hello.wa
# 版权 @2019 凹语言 作者。保留所有权利。

fn main() {
    println(add(40, 2))
}

fn add(a: i32, b: i32) =&amp;gt; i32 {
    return a + b
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下一阶段将继续完善 Playground 和 在线版本的 VSCode 插件，以提供和本地开发完全相同的能力。&lt;/p&gt;

&lt;p&gt;谢谢大家的支持。&lt;/p&gt;</description>
      <author>chai2010-github</author>
      <pubDate>Sat, 17 Sep 2022 14:40:09 +0800</pubDate>
      <link>https://ruby-china.org/topics/42661</link>
      <guid>https://ruby-china.org/topics/42661</guid>
    </item>
    <item>
      <title>关于 GIN 的路由树</title>
      <description>&lt;p&gt;GIN 是一个 golang 常用的 Web 框架，它对 API 比较友好，源码注释也很明确明确，使用起来快速灵活，还有极高的容错率。标题中的路由我们可以简单理解为在浏览器中输入的页面地址，而“树”则是 一种优化的数据结构。因为在 GIN 这个 Web 框架中的路由树是前缀树，所以我们今天会围绕前缀树来讲解。&lt;/p&gt;
&lt;h2 id="什么是前缀树"&gt;什么是前缀树&lt;/h2&gt;
&lt;p&gt;前缀树其实就是 Tire 树，是哈希树的变种，通常大家都叫它单词查找树。前缀树多应用于统计，排序和保存大量字符串。因为前缀树能够利用字符串的公共前缀减少查询时间，最大限度地减少不必要的字符串比较。所以前缀树也经常被搜索引擎系统用于文本词频统计。前缀树拥有以下特点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;根节点不包含字符，其他节点都包含字符&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;每一层的节点内容不同&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;从根节点到某一个节点，路径上经过的字符连接起来，为该节点对应的字符串&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;每个节点的子节点通常有一个标志位，用来标识单词的结束&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以小时候查新华字典为例，我们来直观认识一下前缀树。相信大家都用过音序查字法这种查找方式，其操作内容如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;读准字音，根据该字音节确定应查什么字母。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;在“汉语拼音音节索引”中找到这一字母，在这一字母相应部分找到该字的音节，看清这个音节旁标明的页码。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;按此页码翻开字典的正文，按四声顺序找出所要查的字。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;这整个流程其实可以看做一个粗略的前缀树查找流程&lt;/strong&gt;，比方说要查找成语“心想事成”中的“心想”两字，在字典中即如下结构：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/c03b0918-2c11-45f4-8517-c6ade49d01d0.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;在查找的过程中，我们根据首字母 x，找到 x 当中的 xi 这一共同部分，然后再根据不同的字母找到所对应的剩余部分。放到前缀树查找上，案例中的“心”对应 xi -&amp;gt; n，而“想”则对应 xi -&amp;gt; ang&lt;/p&gt;
&lt;h2 id="GIN中的前缀树-紧凑前缀树"&gt;GIN 中的前缀树 - 紧凑前缀树&lt;/h2&gt;
&lt;p&gt;GIN 中的前缀树相比普通的前缀树减少了查询的层级，比如说上方我们想查找的“心想”其中 xi 做为共有的部分，其实可以被分配在同一层同一个节点当中而不是分为两部分：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/fd21e46a-4049-4705-b5e8-620349db1082.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这样的就是紧凑前缀树，同理如果我们有如下四个路由，他们所形成的紧凑前缀树就会是这个样子：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r.GET("/", handle1)
r.GET("/product", handle2)
r.GET("/product/:id", handle3)
r.GET("/product/:name", handle4)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/2fc4af6d-9d0f-476e-8a26-63446e54fa68.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;在节点中存储信息&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;通过上面的内容可以看出，GIN 中前缀树整条查询的地址只需通过路由树中每个节点的拼接即可获得。那么 GIN 是如何完成在这些节点的增加的呢，每个节点中又存放了什么内容？这个问题我们可以通过 GIN 的源码得到答案。&lt;/p&gt;

&lt;p&gt;首先 GIN 中常用的声明路由的方式如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func main(){
    r := gin.Default()
    r.GET("/", func(context *gin.Context) {
        context.JSON(200, gin.H{
            "status":"ok",
        })
    })
    r.Run()
}

// default会初始化一个engin实例
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

type Engine struct { 
    RouterGroup
        // type RouterGroup struct {
    //    Handlers HandlersChain
        //    basePath string
        //    engine   *Engine
        //    root     bool
        // }
        // 小写私有的，不开放
    trees            methodTrees 
        // ...
}

type methodTrees []methodTree

type methodTree struct {
    method string
    root   *node
}

// trees 路由树这一部分由一个带有method 和root字段的node列表维护
// 每个node代表了路由树中的每一个节点
// node所具有的字段内容如下

type node struct {
    path      string // 当前节点的绝对路径
    indices   string // 缓存下一节点的第一个字符 在遇到子节点为通配符类型的情况下,indices=''
        // 默认是 false，当 children 是 通配符类型时，wildChild 为 true 即 indices=''
    wildChild bool // 默认是 false，当 children 是 通配符类型时，wildChild 为 true

        // 节点的类型，因为在通配符的场景下在查询的时候需要特殊处理， 
        // 默认是static类型
        // 根节点为 root类型
        // 对于 path 包含冒号通配符的情况，nType 是 param 类型
        // 对于包含 * 通配符的情况，nType 类型是 catchAll 类型
    nType     nodeType
        // 代表了有几条路由会经过此节点，用于在节点
    priority  uint32
        // 子节点列表
    children  []*node // child nodes, at most 1 :param style node at the end of the array
    handlers  HandlersChain
        // 是从 root 节点到当前节点的全部 path 部分；如果此节点为终结节点 handlers 为对应的处理链，否则为 nil；
        // maxParams 是当前节点到各个叶子节点的包含的通配符的最大数量
    fullPath  string
}

// 具体节点类型如下
const (
    static nodeType = iota // default， 静态节点，普通匹配(/user)
    root                   // 根节点 (/)
    param                 // 参数节点(/user/:id)
    catchAll              // 通用匹配，匹配任意参数(*user)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加路由则可以通过以下操作：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 在创建路由的过程中， 每一个方法都会最终都会被解析后丢给handle函数去处理
func main(){
    r := gin.Default()
    r.GET("/", func(context *gin.Context) {
        context.JSON(200, gin.H{
            "status":"ok",
        })
    })
    r.Run()
}

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodGet, relativePath, handlers)
}
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodPost, relativePath, handlers)
}

//  handle函数中会将绝对路径转换为相对路径
//  并将 请求方法、相对路径、处理方法 传给addRoute
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := group.calculateAbsolutePath(relativePath)
    handlers = group.combineHandlers(handlers)
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}


// 路由的添加主要在addRoute这个函数中完成
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
   // 校验
   // 路径必须以 / 开头
   // 请求方法不允许为空
   // 处理方法不允许为空
   assert1(path[0] == '/', "path must begin with '/'")
   assert1(method != "", "HTTP method can not be empty")
   assert1(len(handlers) &amp;gt; 0, "there must be at least one handler")

   // 如果开启了gin的debug模式，则对应处理
   debugPrintRoute(method, path, handlers)
   // 根据请求方式获取对应的树的根
   // 每一个请求方法都有自己对应的一颗紧凑前缀树，这里通过请求方法拿到最顶部的根
   root := engine.trees.get(method)
   // 如果根为空，则表示这是第一个路由，则自己创建一个以 / 为path的根节点
   if root == nil {
      // 如果没有就创建
      root = new(node)
      root.fullPath = "/"
      engine.trees = append(engine.trees, methodTree{method: method, root: root})
   }
   // 此处的path是子路由
   // 以上内容是做了一层预校验，避免书写不规范导致的请求查询不到
   // 接下来是添加路由的正文
   root.addRoute(path, handlers)
}
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// addRoute adds a node with the given handle to the path.
// Not concurrency-safe! 并发不安全
func (n *node) addRoute(path string, handlers HandlersChain) {
    fullPath := path
        // 添加完成后，经过此节点的路由条数将会+1
    n.priority++

    // Empty tree
        // 如果为空树， 即只有一个根节点"/" 则插入一个子节点， 并将当前节点设置为root类型的节点
    if len(n.path) == 0 &amp;amp;&amp;amp; len(n.children) == 0 {
        n.insertChild(path, fullPath, handlers)
        n.nType = root
        return
    }

    parentFullPathIndex := 0

walk:
    for {
        // Find the longest common prefix.
        // This also implies that the common prefix contains no ':' or '*'
        // since the existing key can't contain those chars.
                // 找到最长的共有前缀的长度 即到i位置 path[i] == n.path[i]
        i := longestCommonPrefix(path, n.path)

        // Split edge
                // 假设当前节点存在的前缀信息为 hello
                // 现有前缀信息为heo的结点进入， 则当前节点需要被拆分
                // 拆分成为 he节点 以及 (llo 和 o 两个子节点)
        if i &amp;lt; len(n.path) {
            child := node{
                                // 除去公共前缀部分，剩余的内容作为子节点
                path:      n.path[i:],
                wildChild: n.wildChild,
                indices:   n.indices,
                children:  n.children,
                handlers:  n.handlers,
                priority:  n.priority - 1,
                fullPath:  n.fullPath,
            }

            n.children = []*node{&amp;amp;child}
            // []byte for proper unicode char conversion, see #65
            n.indices = bytesconv.BytesToString([]byte{n.path[i]})
            n.path = path[:i]
            n.handlers = nil
            n.wildChild = false
            n.fullPath = fullPath[:parentFullPathIndex+i]
        }

        // Make new node a child of this node
                // 将新来的节点插入新的parent节点作为子节点
        if i &amp;lt; len(path) {
            path = path[i:]
            c := path[0]

            // '/' after param
                        // 如果是参数节点 形如/:i
            if n.nType == param &amp;amp;&amp;amp; c == '/' &amp;amp;&amp;amp; len(n.children) == 1 {
                parentFullPathIndex += len(n.path)
                n = n.children[0]
                n.priority++
                continue walk
            }

            // Check if a child with the next path byte exists
            for i, max := 0, len(n.indices); i &amp;lt; max; i++ {
                if c == n.indices[i] {
                    parentFullPathIndex += len(n.path)
                    i = n.incrementChildPrio(i)
                    n = n.children[i]
                    continue walk
                }
            }

            // Otherwise insert it
            if c != ':' &amp;amp;&amp;amp; c != '*' &amp;amp;&amp;amp; n.nType != catchAll {
                // []byte for proper unicode char conversion, see #65
                n.indices += bytesconv.BytesToString([]byte{c})
                child := &amp;amp;node{
                    fullPath: fullPath,
                }
                n.addChild(child)
                n.incrementChildPrio(len(n.indices) - 1)
                n = child
            } else if n.wildChild {
                // inserting a wildcard node, need to check if it conflicts with the existing wildcard
                n = n.children[len(n.children)-1]
                n.priority++

                // Check if the wildcard matches
                if len(path) &amp;gt;= len(n.path) &amp;amp;&amp;amp; n.path == path[:len(n.path)] &amp;amp;&amp;amp;
                    // Adding a child to a catchAll is not possible
                    n.nType != catchAll &amp;amp;&amp;amp;
                    // Check for longer wildcard, e.g. :name and :names
                    (len(n.path) &amp;gt;= len(path) || path[len(n.path)] == '/') {
                    continue walk
                }

                // Wildcard conflict
                pathSeg := path
                if n.nType != catchAll {
                    pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
                }
                prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
                panic("'" + pathSeg +
                    "' in new path '" + fullPath +
                    "' conflicts with existing wildcard '" + n.path +
                    "' in existing prefix '" + prefix +
                    "'")
            }

            n.insertChild(path, fullPath, handlers)
            return
        }

        // Otherwise add handle to current node
                // 设置处理函数，如果已经存在，则报错
        if n.handlers != nil {
            panic("handlers are already registered for path '" + fullPath + "'")
        }
        n.handlers = handlers
        n.fullPath = fullPath
        return
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Priority 优先级&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;为了能快速找到并组合完整的路由，GIN 在添加路由的同时，会在每个节点中添加 Priority 这个属性。在查找时根据 Priority 进行排序，常用节点 (通过次数理论最多的节点) 在最前，并且同一层级里面 Priority 值越大，越优先进行匹配。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;为何要将 9 种请求方法放在 slice 而不是 map 中&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这是因为 9 个请求方法对应 9 棵路由树，而 GIN 对应的所有请求方法都维护了一颗路由树，同时这些关键信息都被包裹在 Node 结构体内，并被放置在一个数组当中而非 map 中。这样是为了固定请求数量，同时在项目启动后请求方法会被维护在内存当中，采用固定长度的 slice 从而在保证一定查询效率的同时减少内存占用。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type methodTrees []methodTree

func (trees methodTrees) get(method string) *node {
    for _, tree := range trees {
        if tree.method == method {
            return tree.root
        }
    }
    return nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;查找路由&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;路由树构建完毕之后，GIN 即可开始正常接收请求。第一步是从 ServeHTTP 开始解析路由地址，而查找的过程处理逻辑如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;申请一块内存用来填充响应体&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;处理请求信息&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;从 trees 中遍历比较请求方法，拿到最对应请求方法的路由树&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;获取根节点&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    // 真正开始处理请求
    engine.handleHTTPRequest(c)

    engine.pool.Put(c)
}
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (engine *Engine) handleHTTPRequest(c *Context) {
    // ...
    t := engine.trees
    for i, tl := 0, len(t); i &amp;lt; tl; i++ {
        // 根据请求方法进行判断
        if t[i].method != httpMethod {
            continue
        }
        root := t[i].root
        // 在该方法树上查找路由
        value := root.getValue(rPath, c.params, unescape)
        if value.params != nil {
            c.Params = *value.params
        }
        // 执行处理函数
        if value.handlers != nil {
            c.handlers = value.handlers
            c.fullPath = value.fullPath
            c.Next() // 涉及到gin的中间件机制
            // 到这里时，请求已经处理完毕，返回的结果也存储在对应的结构体中了
            c.writermem.WriteHeaderNow()
            return
        }
        // ...
      break
   }
   if engine.HandleMethodNotAllowed {
    for _, tree := range engine.trees {
        if tree.method == httpMethod {
            continue
        }
        if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
            c.handlers = engine.allNoMethod
            serveError(c, http.StatusMethodNotAllowed, default405Body)
            return
        }
    }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面就是关于 GIN 路由树的一些经验分享，希望能够帮助到大家。&lt;/p&gt;</description>
      <author>upyun</author>
      <pubDate>Wed, 22 Jun 2022 10:35:31 +0800</pubDate>
      <link>https://ruby-china.org/topics/42472</link>
      <guid>https://ruby-china.org/topics/42472</guid>
    </item>
    <item>
      <title>Golang 常见设计模式之单例模式</title>
      <description>&lt;p&gt;之前我们已经看过了 Golang 常见设计模式中的装饰和选项模式，今天要看的是 Golang 设计模式里最简单的单例模式。单例模式的作用是确保无论对象被实例化多少次，全局都只有一个实例存在。根据这一特性，我们可以将其应用到全局唯一性配置、数据库连接对象、文件访问对象等。Go 语言实现单例模式的方法有很多种，下面我们就一起来看一下。&lt;/p&gt;
&lt;h2 id="饿汉式"&gt;饿汉式&lt;/h2&gt;
&lt;p&gt;饿汉式实现单例模式非常简单，直接看代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package singleton
type singleton struct{}
var instance = &amp;amp;singleton{}
func GetSingleton() *singleton {
    return instance
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;singleton 包在被导入时会自动初始化 instance 实例，使用时通过调用 singleton.GetSingleton() 函数即可获得 singleton 这个结构体的单例对象。&lt;/p&gt;

&lt;p&gt;这种方式的单例对象是在包加载时立即被创建，所以这个方式叫作饿汉式。与之对应的另一种实现方式叫作懒汉式，懒汉式模式下实例会在第一次被使用时被创建。&lt;/p&gt;

&lt;p&gt;需要注意的是，尽管饿汉式实现单例模式的方式简单，但大多数情况下并不推荐。因为如果单例实例化时初始化内容过多，会造成程序加载用时较长。&lt;/p&gt;
&lt;h2 id="懒汉式"&gt;懒汉式&lt;/h2&gt;
&lt;p&gt;接下来我们再来看下如何通过懒汉式实现单例模式：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package singleton
type singleton struct{}
var instance *singleton
func GetSingleton() *singleton {
    if instance == nil {
        instance = &amp;amp;singleton{}
    }
    return instance
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;相较于饿汉式的实现，懒汉式将实例化 singleton 结构体部分的代码移到了 GetSingleton() 函数内部。这样能够将对象实例化的步骤延迟到 GetSingleton() 第一次被调用时。&lt;/p&gt;

&lt;p&gt;不过通过 instance == nil 的判断来实现单例并不十分可靠，如果有多个 goroutine 同时调用 GetSingleton() 就无法保证并发安全。&lt;/p&gt;
&lt;h2 id="支持并发的单例"&gt;支持并发的单例&lt;/h2&gt;
&lt;p&gt;如果你使用 Go 语言写过并发编程，应该很快能想到该如何解决懒汉式单例模式并发安全问题，比如像下面这样：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package singleton
import "sync"
type singleton struct{}
var instance *singleton
var mu sync.Mutex
func GetSingleton() *singleton {
    mu.Lock()
    defer mu.Unlock()
    if instance == nil {
        instance = &amp;amp;singleton{}
    }
    return instance
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面代码的修改是通过加锁机制，即在 GetSingleton() 函数最开始加了如下两行代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mu.Lock()
defer mu.Unlock()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加锁的机制可以有效保证这个实现单例模式的函数是并发安全的。&lt;/p&gt;

&lt;p&gt;不过使用了锁机制也带来了一些问题，这让每次调用 GetSingleton() 时程序都会进行加锁、解锁的步骤，从而导致程序性能的下降。&lt;/p&gt;
&lt;h2 id="双重锁定"&gt;双重锁定&lt;/h2&gt;
&lt;p&gt;加锁会导致程序性能下降，但又不用锁又无法保证程序的并发安全。为了解决这个问题有人提出了双重锁定（Double-Check Locking）的方案：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package singleton
import "sync"
type singleton struct{}
var instance *singleton
var mu sync.Mutex
func GetSingleton() *singleton {
    if instance == nil {
        mu.Lock()
        defer mu.Unlock()
        if instance == nil {
            instance = &amp;amp;singleton{}
        }
    }
    return instance
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过上面的可以看到，所谓双重锁定实际上就是在程序加锁前又加了一层 instance == nil 判断，通过这种方式来兼顾性能和安全两个方面。不过这让代码看起来有些奇怪，外层已经判断了 instance == nil，但是加锁后又进行了第二次 instance == nil  判断。&lt;/p&gt;

&lt;p&gt;其实外层的 instance == nil 判断是为了提高程序的执行效率，免去原来每次调用 GetSingleton() 都上锁的操作，将加锁的粒度更加精细化。简单说就是如果 instance 已经存在，则无需进入 if 逻辑，程序直接返回 instance 即可。而内层的 instance == nil  判断则考虑了并发安全，考虑到万一在极端情况下，多个 goroutine 同时走到了加锁这一步，内层判断会在这里起到作用。&lt;/p&gt;
&lt;h2 id="Gopher 惯用方案"&gt;Gopher 惯用方案&lt;/h2&gt;
&lt;p&gt;虽然双重锁定机制兼顾和性能和并发安全，但显然代码有些丑陋，不符合广大 Gopher 的期待。好在 Go 语言在 sync 包中提供了 Once 机制能够帮助我们写出更加优雅的代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package singleton
import "sync"
type singleton struct{}
var instance *singleton
var once sync.Once
func GetSingleton() *singleton {
    once.Do(func() {
        instance = &amp;amp;singleton{}
    })
    return instance
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once 是一个结构体，在执行 Do 方法的内部通过 atomic 操作和加锁机制来保证并发安全，且 once.Do 能够保证多个 goroutine 同时执行时 &amp;amp;singleton{} 只被创建一次。&lt;/p&gt;

&lt;p&gt;其实 Once 并不神秘，其内部实现跟上面使用的双重锁定机制非常类似，只不过把 instance == nil 换成了 atomic 操作，感兴趣的同学可以查看下其对应源码。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;以上就是 Go 语言中实现单例模式的几种常用套路，经过对比可以得出结论，最推荐的方式是使用 once.Do 来实现，sync.Once 包帮我们隐藏了部分细节，却可以让代码可读性得到很大提升。&lt;/p&gt;
&lt;h3 id="推荐阅读"&gt;推荐阅读&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.upyun.com/tech/article/702/Golang%20%E5%B8%B8%E8%A7%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E8%A3%85%E9%A5%B0%E6%A8%A1%E5%BC%8F.html" rel="nofollow" target="_blank" title=""&gt;Golang 常见设计模式之装饰模式&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.upyun.com/tech/article/688/Golang%20%E5%B8%B8%E8%A7%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E9%80%89%E9%A1%B9%E6%A8%A1%E5%BC%8F.html" rel="nofollow" target="_blank" title=""&gt;Golang 常见设计模式之选项模式&lt;/a&gt;&lt;/p&gt;</description>
      <author>upyun</author>
      <pubDate>Wed, 01 Jun 2022 10:34:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/42428</link>
      <guid>https://ruby-china.org/topics/42428</guid>
    </item>
    <item>
      <title>分享一个自己的 DDD boilerplate</title>
      <description>&lt;p&gt;如题&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/eedkevin/ddd-boilerplate-go" rel="nofollow" target="_blank"&gt;https://github.com/eedkevin/ddd-boilerplate-go&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;欢迎 star/fork/交流&lt;/p&gt;</description>
      <author>eedkevin</author>
      <pubDate>Thu, 12 May 2022 23:06:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/42390</link>
      <guid>https://ruby-china.org/topics/42390</guid>
    </item>
    <item>
      <title>基于 Rails 设计思想，开发了一个 Go Web 框架，感兴趣可以看看</title>
      <description>&lt;p&gt;地址：&lt;a href="https://github.com/daqing/byeissue" rel="nofollow" target="_blank"&gt;https://github.com/daqing/byeissue&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;目前还是一个雏形，但是基本的组件都有了，包括 MVC + Repository + Migration&lt;/p&gt;

&lt;p&gt;感兴趣可以看看。&lt;/p&gt;

&lt;p&gt;这个项目，既是一个框架，也同时是基于框架开发一个独立的 Issue 管理工具，作为框架的第一个 App。&lt;/p&gt;</description>
      <author>daqing</author>
      <pubDate>Sun, 24 Apr 2022 09:45:57 +0800</pubDate>
      <link>https://ruby-china.org/topics/42338</link>
      <guid>https://ruby-china.org/topics/42338</guid>
    </item>
    <item>
      <title>转：超级轻量级: KV 存储引擎实现🔥</title>
      <description>&lt;p&gt;我觉得这是一个不错的学习 go 和数据库的项目，相对容易理解。&lt;/p&gt;

&lt;p&gt;编程提高主要有两个维度，一个是算法，另一个就是项目。&lt;/p&gt;

&lt;p&gt;下面是项目介绍：&lt;/p&gt;

&lt;p&gt;简 介&lt;/p&gt;

&lt;p&gt;首先要说明的是 Bottle 是一款 KV 嵌入式存储引擎，并非是一款 KV 数据库，我知道很多人看到了 KV 认为是数据库，当然不是了，很多人会把这些搞混淆掉，KV 存储可以用来存储很多东西，而并非是数据库这一领域。可以这么理解数据库是一台汽车，那么 Bottle 是一台车的发动机。可以简单理解 Bottle 是一个对操作系统文件系统的 KV 抽象化封装，可以基于 Bottle 做为存储层，在 Bottle 层之上封装一些数据结构和对外服务的协议就可以实现一个数据库。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gocn.vip/topics/yRBmNgFdon" rel="nofollow" target="_blank" title=""&gt;--&amp;gt;&lt;/a&gt;&lt;/p&gt;</description>
      <author>chenge</author>
      <pubDate>Wed, 09 Mar 2022 09:12:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/42194</link>
      <guid>https://ruby-china.org/topics/42194</guid>
    </item>
    <item>
      <title>golang 函数返回结构体或者结构体指针</title>
      <description>&lt;p&gt;请教一下 golang 函数返回结构体或者结构体指针到底有什么区别？&lt;/p&gt;</description>
      <author>neuman</author>
      <pubDate>Sat, 22 Jan 2022 09:28:42 +0800</pubDate>
      <link>https://ruby-china.org/topics/42095</link>
      <guid>https://ruby-china.org/topics/42095</guid>
    </item>
  </channel>
</rss>
