エラー文「attempt to perform operations with a script private resource on a shared resource」について

# はじめに
VキャスでLuaを触っていると、色々なエラー文に出会うかと思います。
ここでは、珍しいエラー文「attempt to perform operations with a script private resource on a shared resource」に出会ったしまったので、どういったパターンでこのエラー文が発生してしまうのか、説明していきます。
![2023100801042636-1_！入室注意！ わいず実験場_小山内シノブ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985893/a6c4a773-48d5-a050-cffc-f5eb8bd4ba95.jpeg)
# 原因について
今回の原因に関してシンプルに書くと、Vキャス側で予め用意されているmessageがtable型でtable内の要素をtable型で書き換えようとする際に発生するエラーでした。
ここでmessageをどうやって書き換えるのかと、疑問に思われる方がほとんどかと思いますが、これはmessageがtable型で参照渡し、その後渡した側の変数を変更すると、messageを書き換えようとすることとなり、エラーとなります。
ただし、messageを渡した変数自体をtable型で書き換えたり、table内の要素をnumber型で書き換えたりした際には、エラーは発生しませんでした。
ここまでで、恐らくほとんどの方は理解されたかと思いますが、自分はプログラミング何もわからん勢で、Luaを触り始めた頃は「さ、参照渡し。。。？」状態だったので、そんな方達向けに説明していきます！
まずは、値渡しと参照渡しを説明し、続いて、今回のエラーがどのように発生していくのか、具体例を挙げて説明していきます。
# 値渡しと参照渡し
簡単に説明すると、
- 値渡し　　・・・　変数の値を変数に代入する
- 参照渡し　・・・　変数同士を紐づける（参照先を紐づける）

というようなイメージです。
説明を見ているだけでは、理解しにくいかと思いますので、具体例を見ていきましょう。

まずは値渡しの確認から。
結果としては、”b”の値を変えても、”a”の値は影響されず、bは0から1に変更され、aは0のままとなっています。
```lua:main.lua
local a = 0
print("a1 : "..a)
local b = a
print("b1 : "..b)
b = 1
print("a2 : "..a)
print("b2 : "..b)
```
![スクリーンショット (159).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985893/88ffe527-345b-2944-719e-d98182396a7d.png)


続いて、参照渡しの確認を行います。
tableの変数を、別の変数に入れようとした際、参照渡しとなります。
（Luaではそうなるみたいですが、他の言語だとどうなんでしょう？）
”d”のtableの値の変更に影響を受け、d[1]が0から1に変更されると、c[1]も0から1に変更されています。
”c”と”d”が紐づけられた状態となっています。
この紐づけられた先（c）が、messageとなると、エラー文が発生します。
```lua:main.lua
local c = {}
c[1] = 0
print("c1 : "..c[1])
local d = c
print("d1 : "..d[1])
d[1] = 1
print("c2 : "..c[1])
print("d2 : "..d[1])
```
![スクリーンショット (158).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985893/a98114b2-e42a-48fa-06ed-0fd919a07c45.png)

# 参照渡しの回避方法
次の章で実際にエラー文を確認するまでに、参照渡しとならない方法を確認していきます。
参照渡しを回避する方法は、3つあります。（他にもあるかも）
- tableの中身を一つずつ値渡しする
- table.unpackを使う
- 一旦jsonに変換する

以上となります。
それぞれ確認してみましょう。

### tableの中身を一つずつ値渡しする方法
まずは、tableの中身を一つずつ値渡しする方法です。
変数がtable型でなければ、参照渡しにはならないので、数字の状態で渡せば、値渡しとなり、”c”は”d”の影響を受けなくなります。
無事、d[1]を0から1に変更しても、c[1]は0のままとなるようになりました。
ただし、この方法では、tableの中身が増えてきた際に、少し手間となりますよね。
そうした場合は、続く2つの方法で渡すことを考えましょう。
```lua:main.lua
local c = {}
c[1] = 0
print("c1 : "..c[1])
local d = {}
d[1] = c[1]
print("d1 : "..d[1])
d[1] = 1
print("c2 : "..c[1])
print("d2 : "..d[1])
```
![スクリーンショット (160).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985893/a4dcfd4f-9434-2c60-ea0e-dbbe2512a7c7.png)

### table.unpackを使う方法
続いて、table.unpackを使う方法です。
結果は先のものと一緒ですので、写真は省略します。
この方法では、table.unpackを使って、数字で指定されたtableの要素に分解して、｛｝でtable要素に再構築しています。
こちらはdに渡す際に、table要素で渡していますが、一旦、分解・再構築を経ているので、”c”と”d”は影響されない状態となっています。
ただし、table.unpackは、tableのキーが数字で指定されているモノしか、分解されないので、tableのキーが文字列で指定している場合には、使用できません。
そうした場合のは、次の方法を用いると解決できます。
```lua:main.lua
local c = {}
c[1] = 0
print("c1 : "..c[1])
local d = {table.unpack(c)}
print("d1 : "..d[1])
d[1] = 1
print("c2 : "..c[1])
print("d2 : "..d[1])
```
### 一旦jsonに変換する方法
最後の方法は、一旦jsonに変換する方法です。
「json、何それ、美味しいの？」と思う方もいらっしゃると思います（自分もまだそんな状態です）が、いろんなデータをエクスポート・インポートする際のフォーマットの一つと思えばいいかと思います。
tableのデータを、json.serializeで、一旦jsonのデータに変換し、json.parseでtable型に戻しているので、”c”と”d”は影響されない状態となっています。
こちらは、キーが文字列でも対応することができますが、数字と文字列が混合する場合には、数字の要素のみが変換されて、文字列の要素は変換されないようです。
キーに数字と文字列が混在する場合には、少し工夫が必要となります。
```lua:main.lua
local c = {}
c[1] = 0
print("c1 : "..c[1])
local d = json.parse(json.serialize(c))
print("d1 : "..d[1])
d[1] = 1
print("c2 : "..c[1])
print("d2 : "..d[1])
```
# エラー文の再現
さぁ、ここからエラー文の再現を行っていきます。
message関数を使い、tableを飛ばして、元の変数に入れ直して、変更を加えるという操作をしていきます。
最初のほうで説明しましたが、messageを渡した変数自体をtable型で書き換えたり、table内の要素をnumber型で書き換えたりした際には、エラーは発生しなかったので、そちらの確認をまずしていきます。
以下の2つの例が、エラーが発生しない場合です。
```lua:main.lua
local c = {{0}}

function cAddProcess(sender, name, message)
    c = message
    c = {{0}}
end
vci.message.On("cAdd", cAddProcess)

function onUse(use)
    if use == "Obj" then
        vci.message.Emit("cAdd", c)
    end
end
```
```lua:main.lua
local c = {{0}}

function cAddProcess(sender, name, message)
    c = message
    c[1][1] = 0
end
vci.message.On("cAdd", cAddProcess)

function onUse(use)
    if use == "Obj" then
        vci.message.Emit("cAdd", c)
    end
end
```
続いて、エラーが発生する場合です。
最初のほうで説明しましたが、table型のmessageを変数（c）に入れ、変数内の要素を、table型で変更を行いにいくとエラーとなります。
```lua:main.lua
local c = {{0}}

function cAddProcess(sender, name, message)
    c = message
    c[1] = {0}
end
vci.message.On("cAdd", cAddProcess)

function onUse(use)
    if use == "Obj" then
        vci.message.Emit("cAdd", c)
    end
end
```

このエラーに対して、先にご紹介した「参照渡しの回避方法」を使っていくと、エラーを回避できるようになります。
ここでは、一旦jsonに変換する方法でエラーを回避します。
一応、参照渡しを回避する以外にも、c[1]=message[1]で1次元の配列に落として渡せば、エラーにならないことは確認できていますが、不要な参照渡しは回避したほうがよいので、messageでtable型を受け渡す場合には、上記の「参照渡しの回避方法」を用いたほうがよいかと思います。
```lua:main.lua
local c = {{0}}

function cAddProcess(sender, name, message)
    c = json.parse(json.serialize(message))
    c[1] = {0}
end
vci.message.On("cAdd", cAddProcess)

function onUse(use)
    if use == "Obj" then
        vci.message.Emit("cAdd", c)
    end
end
```

# おわりに
ここでは、エラー文「attempt to perform operations with a script private resource on a shared resource」を回避する方法として、参照渡しの回避方法を説明してきました。
エラーを回避する以外にも、table型を処理していく上で、参照渡しを経由して、意図しない動作が発生してしまうことがあります。（自分も麻雀VCIを作る上で、大変悩まされました！）
そんな折には、上記で説明した「参照渡しの回避方法」を思い出してみてくださいね！