首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Solidity Library 中的常见报错与设计思考

Solidity Library 中的常见报错与设计思考

原创
作者头像
孟斯特
发布2025-09-25 09:44:06
发布2025-09-25 09:44:06
2340
举报
文章被收录于专栏:EthereumEthereum

在学习和使用 Solidity 时,很多人第一次接触 library 的时候,都会遇到这样的报错信息:

代码语言:javascript
复制
TypeError: Name has to refer to a user-defined type

为什么会报这个错?为什么库函数经常被设计为使用 storage 引用?现在我们就通过一个实验来展示 storagememory 的实际区别。


一、报错:Name has to refer to a user-defined type

在 Solidity 中,library 有两种典型的使用方式:

  1. 直接调用库函数
  2. 通过 using ... for 给类型扩展方法

第二种方式是大家常用的,例如:

代码语言:solidity
复制
struct Data { uint value; }

library DataLib {
    function increment(Data storage self) public {
        self.value += 1;
    }
}

contract C {
    using DataLib for Data;

    Data public d;

    function test() public {
        d.increment(); // 自动传入 d
    }
}

但是,如果你写成:

代码语言:solidity
复制
library DataLib {
    function increment(uint storage self) public { 
        self += 1;
    } 
}

编译时就会报错:

代码语言:javascript
复制
TypeError: Name has to refer to a user-defined type

原因在于:通过 using ... for 的库函数,第一个参数必须是用户自定义类型(struct 或 enum),而不能是 uintaddress 等内置类型。


二、为什么库函数使用 storage 引用?

在 Solidity 中,参数有三种数据位置:

  • storage —— 持久化存储在区块链上的数据
  • memory —— 函数调用过程中的临时内存
  • calldata —— 外部调用时的只读数据区

如果库函数参数写成 memory

代码语言:solidity
复制
function increment(Data memory self) public {
    self.value += 1;
}

调用时会复制一份数据,函数里对副本的修改不会影响合约状态。

而如果使用 storage

代码语言:solidity
复制
function increment(Data storage self) public {
    self.value += 1; // 直接修改合约里的状态
}

那么修改会直接作用在合约的存储上,符合大多数“扩展方法”的预期。

好处:

  • 避免复制大型数据结构,节省 gas
  • 语义更接近“对象方法”,调用时就像在修改原对象
  • 确保对合约状态的修改是持久的

三、对比实验:storage vs memory

下面我们通过一个小实验来直观感受差异:

代码语言:solidity
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

struct Counter {
    uint count;
}

library CounterLib {
    // 使用 storage,能修改合约状态
    function incStorage(Counter storage self) public {
        self.count += 1;
    }

    // 使用 memory,只会修改副本,不影响状态
    function incMemory(
        Counter memory self
    ) public pure returns (Counter memory) {
        self.count += 1;
        return self;
    }
}

contract C {
    using CounterLib for Counter;

    Counter public counter;

    // 调用 storage 版本
    function callStorage() public {
        counter.incStorage();
    }

    // 调用 memory 版本
    function callMemory() public view {
        Counter memory temp = counter;
        temp = temp.incMemory(); // 只改了副本
        // counter 本身并没有被改变
    }

    function getCount() public view returns (uint) {
        return counter.count;
    }
}

测试过程

使用Foundry编写测试脚本 Counter.t.sol

代码语言:solidity
复制
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test} from "forge-std/Test.sol";
import "../src/Counter.sol";

contract CTest is Test {
    C public counter;

    function setUp() public {
        counter = new C();
    }

    function testIncStorage() public {
        counter.callStorage();
        assertEq(counter.getCount(), 1);
    }

    function testIncMemory() public view {
        counter.callMemory();
        assertEq(counter.getCount(), 0);
    }
}

执行测试脚本:

代码语言:bash
复制
➜  tutorial git:(main) ✗ forge test --match-path test/Counter.t.sol -vvv
[⠊] Compiling...
[⠔] Compiling 1 files with Solc 0.8.30
[⠒] Solc 0.8.30 finished in 430.02ms
Compiler run successful!

Ran 2 tests for test/Counter.t.sol:CTest
[PASS] testIncMemory() (gas: 13501)
[PASS] testIncStorage() (gas: 32255)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.19ms (1.03ms CPU time)

Ran 1 test suite in 155.29ms (3.19ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)

四、总结

  1. 报错原因library 扩展函数的第一个参数必须是用户自定义类型(structenum),否则会报错 “Name has to refer to a user-defined type”
  2. 为什么用 storage
    • storage 允许函数直接修改合约状态
    • 避免大数据复制,节省 gas
    • 语义更自然,像对象方法一样作用于原数据
  3. 实验对比
    • storage 参数:修改持久化状态
    • memory 参数:只修改副本,不会影响状态

所以在设计库函数时,如果你希望修改合约的状态变量,必须使用 storage 引用;如果只是做临时计算,可以用 memorycalldata

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、报错:Name has to refer to a user-defined type
  • 二、为什么库函数使用 storage 引用?
  • 三、对比实验:storage vs memory
    • 测试过程
  • 四、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档