为什么给 map 赋值也「可能失败」?:效果系统的设计争论
往 map 里写一行,要么新增要么覆盖,怎么看都不该「走不通」——可 Epic 官方论坛上真有人替你把这个问题拍到了桌面上。这一页带你围观那场争论,顺便看懂 Verse 为什么给这么多操作都盖上「可能走不通」的戳。
一、一个理直气壮的疑问
开发者 ZeroYaHero 在 Epic 官方论坛发帖:为什么 if (set MyMap[Key] = Value) 这句非得接进 Branch 不可?按直觉,给 map 的某个键写值,结局只有两种——键不存在就新增,键存在就覆盖,横竖都成,哪来的「走不通」?可 Verse 的 Compile 铁面无私:往 map 的某个键写值这件事带着「可能走不通」的戳,不接进 Branch 就不给过,亮红报的正是那条经典错误:「This invocation calls a function that has the 'decides' effect, which is not allowed by its context.」
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
failable_set_demo := class(creative_device):
var Scores:[string]int = map{}
OnBegin<override>()<suspends>:void =
# 编译错误:'decides' effect not allowed by its context
# set Scores["A"] = 1
# 正确姿势:包进失败上下文
if (set Scores["A"] = 1):
Print("A 登记成功")
# 复合赋值是真的会失败:B 从未登记,先读就挂了
if (set Scores["B"] += 1):
Print("这行不会执行")
else:
Print("B 不存在,+= 失败并回滚")
读图翻译:一个 Event BeginPlay 里三段。第一段是被注释掉的反面教材——直接写 set Scores["A"] = 1 不接 Branch,过不了 Compile。第二段是正确写法:接进 Branch,走通就 Print String 打「A 登记成功」。第三段对从没登记的「B」做「在原值上加 1」,「读」那步查无此人,走 else 打出失败提示——注意:走不通的那条线上,已经试着写进去的改动会被整个撤销(回滚),就像从没发生过。
二、社区的解释:被「在原值上加」连坐
回帖里最有说服力的解释来自 Sprintermax:关键在 +=、-= 这些「在原值上加/减」的写法。set M[K] += 1 必须先把旧值读出来,算完再写回去;键不存在时,「读」那一步实打实会走不通——这不是设计洁癖,是逻辑必然。而 Verse 把「往 map 的某个键写值」当作一整个家族看待:最普通的「直接写」和「在原值上加」共用同一套写入机制,于是全家统一盖上「可能走不通」的戳,一个都不例外。
也有回帖猜测原因藏在引擎内部实现里,比如多条线同时访问时的安全处理。讨论最后的结论相当坦率:最普通的「直接写」逻辑上可以不「走不通」,但语言就是没这么设计。换句话说,这不是 Compile 的 bug,而是语言作者刻意选择的一致性。
三、这套「可能走不通」的戳在下一盘大棋
把镜头拉远,你会看到 Verse 的核心哲学:「这个操作可能走不通」不靠运行时报错崩游戏来表达,而是明明白白盖在这个操作的「铭牌」上——也就是那个「可能走不通」的戳。铭牌统一了,你的心智模型就统一了:凡是用方括号按位置 / 按键存取的操作,array 读、array 写、map 读、map 写,一律可能走不通、一律接进 Branch。你永远不用背「哪个会走不通、哪个不会」的例外清单。
代价是什么?偶尔要给「明明不会走不通」的操作也套一层 Branch,看起来啰嗦。收益呢?所有可能走不通的岔路在点 Compile 时就摆上台面,还附赠一个「回滚」福利——这就像先在一张「影子图」上把节点试连一遍,只要这条线走不通,试连时写进去的改动当场全部作废,真正的图纹丝不动。语言设计常常要在「局部直觉」和「全局一致」之间二选一,Verse 在这里毫不犹豫地选了后者。
实践上的小抄:不需要 else 那一侧时,用单行 if (set M[K] = V) {} 收尾,那对空 {} 就是「else 什么都不做」——看的人会把它当作「一次必然成功的写入」,形式上服从规则,心理上不增加负担。
按论坛讨论的主流解释,对不存在的键 K 做 set Scores[K] += 1(在原值上加 1)为什么会走不通?
来源
本文整理自 Epic 开发者论坛与官方文档:
▸ Why is setting a key value in a map type failable in Verse?(Epic 开发者论坛)↗
▸ Map in Verse(官方文档)↗