惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
Simon Willison's Weblog
Simon Willison's Weblog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Proofpoint News Feed
A
Arctic Wolf
T
Threat Research - Cisco Blogs
Apple Machine Learning Research
Apple Machine Learning Research
V
Visual Studio Blog
博客园 - Franky
Cyberwarzone
Cyberwarzone
宝玉的分享
宝玉的分享
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tailwind CSS Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Scott Helme
Scott Helme
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Cisco Blogs
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
AWS News Blog
AWS News Blog
IT之家
IT之家
MongoDB | Blog
MongoDB | Blog
人人都是产品经理
人人都是产品经理
The Cloudflare Blog
Know Your Adversary
Know Your Adversary
腾讯CDC
Microsoft Security Blog
Microsoft Security Blog
博客园_首页
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Vercel News
Vercel News
Recorded Future
Recorded Future
Engineering at Meta
Engineering at Meta
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 司徒正美
C
Check Point Blog
T
The Exploit Database - CXSecurity.com
I
Intezer
P
Palo Alto Networks Blog
爱范儿
爱范儿
The Hacker News
The Hacker News
Microsoft Azure Blog
Microsoft Azure Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
S
Securelist
Security Latest
Security Latest
The GitHub Blog
The GitHub Blog
H
Help Net Security
B
Blog RSS Feed
量子位
Martin Fowler
Martin Fowler
I
InfoQ

Yesterday17's Blog

2026 新年解密红包 / Melody Flag | Yesterday17's Blog 谈谈 Iori 的设计思路(二):如何实现一个 Showroom 录制工具? | Yesterday17's Blog 谈谈 Iori 的设计思路(一):从 Nico Timeshift 说起 | Yesterday17's Blog Iori Minyami 0.1.0 发布 | Yesterday17's Blog 2025 新年解密红包 / Melody Flag | Yesterday17's Blog 使用 Cloudflare Warp 解决罗森票务的海外登录问题 | Yesterday17's Blog How To Blog 04: The Astro v5 Era | Yesterday17's Blog 谈谈 tokio::select! 的公平性 | Yesterday17's Blog Learning Pingora 05 - Connect with TLS | Yesterday17's Blog Leaving Bytedance | Yesterday17's Blog 大橋彩香 AsiaTour「Reflection」上海公演 个人向记录 & Repo | Yesterday17's Blog Recoving from burnout - What happened? | Yesterday17 Yubikey 重建手册 | Yesterday17's Blog How To Blog 03: Heimus | Yesterday17's Blog 🪧 Blog Migration Accouncement | Yesterday17's Blog Learn Your IDE - VSCode 是如何仅重启插件的? | Yesterday17's Blog How To Blog 02: Astro❤️Password | Yesterday17's Blog How To Blog 01: Why, How, and the Future | Yesterday17's Blog Learning Pingora 04 - Establish L4 Connection | Yesterday17's Blog Learning Pingora 03 - Upstreams and Peers | Yesterday17's Blog Learning Pingora 02 - A Simple HTTP Server | Yesterday17's Blog Learning Pingora 01 - Getting Started | Yesterday17's Blog 2024 新年解密红包 / Melody Flag | Yesterday17's Blog 向新的一年飞驰——记录 2023 | Yesterday17's Blog 「サクラノ刻」对话选摘(2) | Yesterday17's Blog PGP Key Revocation 注销声明 | Yesterday17's Blog 「サクラノ刻」对话选摘(1) | Yesterday17's Blog 2023 新年解密红包 / Melody Flag | Yesterday17's Blog 『蒼の彼方のフォーリズム』通关感想 | Yesterday17's Blog 单显卡直通教程 | Yesterday17's Blog 对博客与笔记的思考 | Yesterday17's Blog Project Anni 之旅(3)自动化 Flutter 应用 CI/CD 上架流程 | Yesterday17's Blog AsobiStage 直接播放链接 | Yesterday17 如何在后分P时代进行投稿——sswa使用详解 | Yesterday17's Blog JSON RPC 与 LSP 协议基础 | Yesterday17's Blog Grajapa Shueisha / BookEnd 加密方式调查 | Yesterday17's Blog 【2022篇+WriteUp】如何再收一个新年红包? | Yesterday17's Blog 如何将良心云的良心功能清理干净 | Yesterday17's Blog 【油猴脚本】bilibili 投稿页面返回旧版+旧版页面强制允许分P上传 | Yesterday17's Blog Cloudr1v1 授权方式分析 | Yesterday17 Typora 1.0.2 逆向实录 | Yesterday17's Blog Project Anni 之旅(2)ValueAfterTable——toml-rs的实现与限制 | Yesterday17's Blog IPv4透明代理+IPv6 Passthrough——树莓派单臂软路由折腾记 | Yesterday17's Blog Chaos; Child 汉化补丁 神秘编码探索 | Yesterday17's Blog 镣铐与舞蹈——个性与共性之迷思 | Yesterday17's Blog Go 学习笔记 02 - 找准 io 之道 | Yesterday17's Blog NAT Slipstreaming v1 原理浅析 | Yesterday17's Blog 绕过「9-nine-」的 CDKEY 验证——KrkrPlugin 正(?)向实录 | Yesterday17's Blog 静流的青春纪念册——「サクラノ刻 -櫻の森の下を歩む-」体验版感言 | Yesterday17's Blog Project Anni 之旅 01 - 从 clap-builder 到 derive | Yesterday17's Blog [Google CTF 2021] CPP WriteUp | Yesterday17's Blog 获取 アソビステージ 的实际播放链接 | Yesterday17's Blog 90 行 Rust 代码实现 AsyncTeeReader | Yesterday17's Blog 或许还算有价值一读的文章列表 | Yesterday17's Blog 从零开始的 Seedbox 之旅 | Yesterday17's Blog [随笔]技术型博客行文迷思(1) | Yesterday17's Blog 浅谈 git fetch 的工作方式 | Yesterday17's Blog 『ソーサレス*アライヴ! ~the World's End Fallen Star~』通关感想" | Yesterday17's Blog Rust std::fmt 格式语法简述 | Yesterday17's Blog 日亚修改居住国的解决方案 | Yesterday17's Blog [Windows/Linux] GC553 的 Switch 完美采集之路 | Yesterday17's Blog 【翻译】Subtyping and Variance / 子类型与变型 | Yesterday17's Blog Berd's Red Envelope 2021 WriteUp | Yesterday17's Blog 【中英对照】ALSA 音频 API 使用教程/A Tutorial on Using the ALSA Audio API | Yesterday17's Blog 从 cue_scanner.l 看 CUE Sheet 的词法单元 | Yesterday17's Blog Postman 历史记录导出的解决方案 | Yesterday17's Blog 《恋爱绮谭 不存在的夏天》通关感想 | Yesterday17's Blog [微机实验/TD-PITE] 微机接口综合实验 | Yesterday17's Blog [微机实验/TD-PITE] 键盘扫描及数码管显示实验 | Yesterday17's Blog [微机实验/TD-PITE] 数码管显示实验 | Yesterday17's Blog Airsonic Advanced+Google Drive+Caddy 部署纪实 | Yesterday17's Blog X-NUCA 2020 - hellowasm 题解 | Yesterday17's Blog [微机实验/TD-PITE] 8251 串行接口实验 | Yesterday17's Blog Node.js child_process.fork 与 env 污染 RCE | Yesterday17's Blog EP.01 「夜の向日葵」 | Yesterday17's Blog [微机实验/TD-PITE] 8254 定时/计数器实验+选做实验 | Yesterday17's Blog [JLU CTF/2020] babywasm WriteUp | Yesterday17's Blog PHP 反序列化与经典利用 | Yesterday17's Blog WebAssembly 逆向简述 | Yesterday17's Blog 『彼女、お借りします』一期完结点评 | Yesterday17's Blog [微机实验/TD-PITE] D/A 转换实验+选做实验 | Yesterday17's Blog [微机实验/TD-PITE] A/D 转换实验+选做实验 | Yesterday17's Blog 开源项目申请 JetBrains Open Source License 简单流程 | Yesterday17's Blog 微软拼音与 JetBrains 搜索快捷键冲突的解决方案 | Yesterday17's Blog [微机实验/TD-PITE] 8259 中断优先级实验+选做实验 | Yesterday17's Blog IFTTT 测试(续) | Yesterday17 IFTTT 测试 | Yesterday17's Blog [微机实验/TD-PITE] 存储器扩展实验+选做实验 | Yesterday17's Blog 新版 GCC 针对 -fdump-translation-unit 的替代方案 | Yesterday17's Blog 一次 HSTS 策略配置的排错之旅 | Yesterday17's Blog YukiNative 踩坑记——Windows 的消息队列 | Yesterday17's Blog 我是我自己——论获取 HTTPS 证书时的验证步骤 | Yesterday17's Blog 【设计文档】对 PUG 的大规模设计修订(1.1) | Yesterday17's Blog GS65 折腾记(2)加装固态,分区,Grub2 引导 Manjaro LiveCD | Yesterday17's Blog 「さくら、もゆ。」的空白字体列表——一次逆向问题定位过程实录 | Yesterday17's Blog GSuite 探索篇(1)使用 Service Account 向 Google Drive 传输文件 | Yesterday17's Blog 『サクラノ詩 -櫻の森の上を舞う-』通关感想 | Yesterday17's Blog 《ATRI -My Dear Moments-》通关感想 | Yesterday17's Blog [工具][VSCode 扩展] AegiKit——方便 Aegisub 使用的工具箱 | Yesterday17's Blog 贝塞尔曲线、字体矢量化与曲线运算 | Yesterday17's Blog
Go 学习笔记 01 - 找准反射之道 | Yesterday17's Blog
Yesterday17 · 2020-02-18 · via Yesterday17's Blog

最近准备好好的补一下 Go 的基础内容,正好反射这一点也是我比较关注的。

之前在 clash 那里发的 issue 就遇到了这样一个 golang 经典陷阱:Typed nilnil 并不相等,使用 xxx != nil 会得到永真的结果。这个问题的暂时解决方案就和反射有关:

if reflect.ValueOf(addr).IsNil() {

// operation

}

当然了,这样的解决方案是暂时的,我们不能在每个这样的场合都通过反射来解决问题,但这勾起了我对 Go 反射的兴趣。而想要了解反射,reflect给了我们一个不错的建议

读完全文,深有感触。正所谓想要学好反射,不仅在于找准反射之道,关键在于找准反射之道。那我们就开始吧(

简介

无论你听说过反射或否,在这里我们简单的给出一个定义:反射是在运行时获取/修改对象的一种手段。我们知道我默认你们知道,通过 Java 的反射,我们可以获取、修改 private,甚至是 final 的内容,这一点对于 golang 来说也不例外。只是与 Javaclass 机制不同,golang 的类型是零散的,因此无法做到像 Java 那么“动态”。

类型(Type)

Go 的类型,相信大家都很熟悉了。在平时使用的过程中,我们已经习惯了这样的使用:

type AAA struct {

// struct body

}

type B AAA // here B is alias to AAA

// Think the comment above, is it right?

正如最后一行注释所问,B 真的是 AAA 的别名吗?答案是:是、但也不是。这里我们先解释“不是”的原因,而“”的部分则留给下文来细细阐述。

我们知道,如果定义了一个类型的“别名”,那这时候,想要把它直接赋值给原类型是无法隐式做到的,你必须显式地完成这一转换过程。那是因为 Go 是一门静态类型的语言,而别名前后的类型是不一样的。从这个角度来讲,B 并不是 AAA 的别名,它只是和 A 结构相同的兄弟(きょうだい)而已。

前置知识

接口(interface)

在介绍 reflect 之前,我们先提出一个问题:你们认识中的 interface 是什么样的?

不少人可能会带有 Java 遗留下来的固有印象:interface 是那个 implements 的东西,在 golang 里不需要 implements 了,所以更加灵活。

这样的理解,怎么说呢,之前我也是这么认为的,各个语言的 interface 总该差不多吧。但在读完这篇文章之后,我发现我错的离谱。

Go 中的 interface 是什么呢?

实质上,它只是一个方法的集合。在接口中定义了一定数量的方法(数量也可以为0),而实现了这些方法的类型称作实现了这个接口。

有一种说法认为,GoJava 接口的区别在于 Java 需要显式地声明实现,而 Go 是隐式的。我认为这种说法是表现上的事实事实上的误导。我们不妨做一个实验:

package main

import "fmt"

type InterfaceA interface {

Write(data string) error

}

type InterfaceB interface {

Read() string

Write(data string) error

}

type Implement string

func (i *Implement) Read() string {

return string(*i)

}

func (i *Implement) Write(data string) error {

*i = Implement(data)

return nil

}

func main() {

var impl Implement = "test"

var ia InterfaceA = &impl

_ = ia.Write("After InterfaceA.")

var ib InterfaceB = &impl

fmt.Println(ib.Read())

_ = ib.Write("After InterfaceB.")

fmt.Println(impl)

}

在这个例子中,我们定义了两个 interface 和一个 struct。大家可能发现了,B 其实是包含了 A 的,这个例子的输出也一点都不出乎意料。那通过这个例子可以表现出什么呢?

它揭示出了这样的一个事实:Go 的接口实质是方法的集合,只要方法定义相同,那它们就是相同的

在这个基础上,我们再回来看这样一个特殊的类型:interface{}。是不是有点感觉了?这其实就是一个没有任何方法的接口,从方法的集合角度来看,这就是一个方法的空集。正因为空集是所有集合的子集,因此任何接口类型都可以转换为 interface{} 类型。

这样的结构也决定了 Go 的接口是真正意义上的“接口”:只要两个口子使用的标准一样、尺寸一样、参数一样,那它就是一样的,而无关生产厂家(声明在何处,以何名称声明);所有生产出来的产品(Type),只要它符合这个标准,那它就可以说是这个标准下的“同类产品”。

说完了这件最重要的事情,最后补充一点大家都知道的吧。接口是静态类型的存在;接口内存储的是指针类型;接口存储的类型是其定义类型或者 nil

反射

终于,我们到了本文的主体部分,但某种意义上也是最为简单的部分(当然前提是上文的内容你都理解了)。

如果按照那篇博客的方式来讲的话,它将反射之道归结成了三条规则;而在本文,我们希望摆脱这样的束缚,因为这三条规则并不平衡。我们把它们罗列出来:

  • 反射使我们能够通过接口到反射对象
  • 反射使我们能够从反射对象返回到接口
  • 要修改的反射对象必须可修改的。

从这三条来看,其实前两条更像是“设计准则”一样的存在,和第三条相比,其重要性完全不同。因此,这里我们不完全遵循原文的讲解思路。我会用我理解的重要程度对反射作出描述。

接口的值(Value)

不知道大家还记不记得上文留下来的这个问题:

B 真的是 AAA 的别名吗?

上文中,我们回答了“不是”的原因,这里我们来回答“是”的理由。观察如下代码:

package main

import (

"fmt"

"reflect"

)

type float114514 float64

func main() {

var f1 float114514 = 1919.810

v := reflect.ValueOf(f1)

fmt.Println(v.Kind())

}

这段代码的输出是什么呢?

float64

这就涉及到了反射中的一个概念:Kind,以及我自认为这门语言最浪漫的地方。

如果你去看 Type 的定义,你会看到这样的一段内容:

// A Kind represents the specific kind of type that a Type represents.

// The zero Kind is not a valid kind.

type Kind uint

const (

Invalid Kind = iota

Bool

Int

Int8

Int16

Int32

Int64

Uint

Uint8

Uint16

Uint32

Uint64

Uintptr

Float32

Float64

Complex64

Complex128

Array

Chan

Func

Interface

Map

Ptr

Slice

String

Struct

UnsafePointer

)

第一行的注释清晰地告诉了我们一个试试:下面罗列的所有 Kind,就是这门语言中所有的真实类型。不论你具体如何书写,到最后都可以表现为这些元素的相互运算。这也是反射之所以能够存在的原因,正因为这个世界是可知的,我们才可以以这样仿佛“动态”的方式去完成我们想做的任务。

所以这时候如果你问我它是别名吗?我想,答案就如同上文所讲的那般不确定了。是也不是,这是对这个问题最好的回答。

可以改变的极限(CanSet)

这条对应的就是那三条中的第三条,因为这条确实重要,因此我也遵循原文的思路讲起。

我们知道,反射的一大作用就是动态修改。但有些东西是不能修改的。拿 C 来举例子吧,C 中的字符串字面量就是无法修改的,其在编译时就确定了。Go 虽然和其不大一样,但也存在反射无法修改的东西。

回顾我们学习的编程语言,赋值是基础中的基础。在赋值时,任何一本教材都会这样教给我们:[Variable Name] = <ToAssign>,而反之则不一定成立。这是因为 <ToAssign> 可能为数字,而单纯的修改数字“字面量”是非法的。

到了 Go 中,又多了一种情况:试图修改非指针的类型。这里我们拿原文的例子:

var x float64 = 3.4

v := reflect.ValueOf(x)

v.SetFloat(7.1) // Error: will panic.

这里会抛出 panic 的原因很简单:x 不是指针,reflect.ValueOf 获得的其实和直接在 x 处写 3.4 的效果是一样的。如果要判断某种情况下是否可写,可以通过 v.CanSet() 完成。

那什么内容是可写的呢?内存中的某一段是可写的。因此这就需要我们在传入 ValueOf 时传入一个指针,然后修改其指向的内容。在 Go 中,通常是这样完成的:

var x float64 = 3.4

p := reflect.ValueOf(&x) // Note: take the address of x.

v := p.Elem()

v.SetFloat(7.1)


最后摘一句原博中的话吧:

Reflection can be hard to understand but it’s doing exactly what the language does, albeit through reflection Types and Values that can disguise what’s going on.

但对我而言,我觉得这句话可以改成:

Reflection is romantic because it’s doing exactly what the language does. Through reflect Types and Values you can know exactly what’s going on.

それではww