a

Câu hỏi Xuất khẩu các hàm với cấu trúc ẩn danh như một tham số [không thể sử dụng giá trị (type struct {…}) dưới dạng struct {…} trong đối số tới package.Func]


Đây là một đoạn mã play.google.org chạy mà không có bất kỳ vấn đề nào:

package main

import (
    "fmt"
)

func PrintAnonymous(v struct {
    i int
    s string
}) {
    fmt.Printf("%d: %s\n", v.i, v.s)
}

func PrintAnonymous2(v struct{}) {
    fmt.Println("Whatever")
}

func main() {
    value := struct {
        i int
        s string
    }{
        0, "Hello, world!",
    }
    PrintAnonymous(value)
    PrintAnonymous2(struct{}{})
}

Tuy nhiên, nếu PrintAnonymous() chức năng tồn tại trong một gói khác (giả sử, temp), mã sẽ không hoạt động:

cannot use value (type struct { i int; s string })
as type struct { i int; s string } in argument to temp.PrintAnonymous

Câu hỏi của tôi là:

  • Có cách nào để gọi một hàm (public) với cấu trúc nặc danh như một tham số (a.k.a. PrintAnonymous() ở trên)?
  • Một hàm có cấu trúc rỗng làm tham số (a.k.a. PrintAnonymous2() ở trên) có thể được gọi ngay cả khi nó tồn tại trong một gói khác. Đây có phải là trường hợp đặc biệt không?

Vâng, tôi biết rằng tôi luôn có thể đặt tên cho struct để giải quyết vấn đề, tôi chỉ tò mò về điều này, và tự hỏi tại sao có vẻ như điều này không được phép.


14
2017-08-05 08:44


gốc


Điều gì sẽ xảy ra nếu bạn ở trên trường hợp các thuộc tính của cấu trúc? struct {I int S string} - Dale
@Dale: Vâng! Tôi chỉ nhận thấy điều này! Cảm ơn! Tôi thật ngốc nghếch! - Siu Ching Pong -Asuka Kenji-


Các câu trả lời:


Các trường của loại cấu trúc ẩn danh của bạn không được xuất hiện. Điều này có nghĩa là bạn không thể tạo các giá trị của cấu trúc này và chỉ định các giá trị cho các trường từ một gói khác. Spec: Composite literals:

Đó là một lỗi để chỉ định một phần tử cho một trường không được xuất của một cấu trúc thuộc về một gói khác.

Nếu bạn thay đổi định nghĩa struct để xuất các trường, thì nó sẽ hoạt động vì tất cả các trường có thể được gán bởi các gói khác (xem câu trả lời của Siu Ching Pong -Asuka Kenji-)).

Đây là trường hợp với cấu trúc rỗng (không có trường): cấu trúc trống không có trường, do đó nó không có trường chưa được xuất hiện, vì vậy bạn được phép chuyển giá trị đó.

Bạn có thể gọi hàm với cấu trúc chưa sửa đổi (với các trường chưa được xuất hiện) qua sự phản chiếu Tuy nhiên. Bạn có thể lấy reflect.Type của PrintAnonymous() và bạn có thể sử dụng Type.In() để có được reflect.Type của tham số đầu tiên. Đó là cấu trúc ẩn danh mà chúng ta muốn chuyển một giá trị cho. Và bạn có thể xây dựng một giá trị của loại đó bằng cách sử dụng reflect.New(). Đây sẽ là một reflect.Value, gói một con trỏ đến giá trị bằng không của cấu trúc ẩn danh. Xin lỗi, bạn không thể có giá trị struct với các trường có giá trị khác 0 (vì lý do được đề cập ở trên).

Đây là cách nó có thể trông giống như:

v := reflect.ValueOf(somepackage.PrintAnonymous)
paramt := v.Type().In(0)
v.Call([]reflect.Value{reflect.New(paramt).Elem()})

Điều này sẽ in:

0: 

0 là giá trị bằng không cho int"" chuỗi rỗng cho string.

Để sâu hơn bên trong vào hệ thống kiểu và cấu trúc với các trường chưa được xuất hiện, hãy xem các câu hỏi liên quan:

Xác định các kiểu nội trang không sử dụng phản ánh
Làm thế nào để sao chép một cấu trúc với trường chưa được xuất bản?


Thật thú vị (đây là một lỗi, sử dụng sự phản chiếu, bạn có thể sử dụng giá trị của kiểu cấu trúc ẩn danh của riêng bạn (với các trường phù hợp, không được xuất hiện) và trong trường hợp này chúng ta có thể sử dụng các giá trị khác với giá trị 0 của các trường struct:

value := struct {
    i int
    s string
}{
    1, "Hello, world!",
}

v.Call([]reflect.Value{reflect.ValueOf(value)})

Trên chạy (không hoảng sợ):

1: Hello, world!

Lý do tại sao điều này được cho phép là do một lỗi trong trình biên dịch. Xem mã ví dụ bên dưới:

s := struct{ i int }{2}

t := reflect.TypeOf(s)
fmt.Printf("Name: %q, PkgPath: %q\n", t.Name(), t.PkgPath())
fmt.Printf("Name: %q, PkgPath: %q\n", t.Field(0).Name, t.Field(0).PkgPath)

t2 := reflect.TypeOf(subplay.PrintAnonymous).In(0)
fmt.Printf("Name: %q, PkgPath: %q\n", t2.Name(), t2.PkgPath())
fmt.Printf("Name: %q, PkgPath: %q\n", t2.Field(0).Name, t2.Field(0).PkgPath)

Đầu ra là:

Name: "", PkgPath: ""
Name: "i", PkgPath: "main"
Name: "", PkgPath: ""
Name: "i", PkgPath: "main"

Như bạn có thể thấy trường chưa được xuất bản i trong cả hai loại cấu trúc ẩn danh (trong main gói và trong somepackage làm tham số PrintAnonymous()chức năng) –falsely– báo cáo cùng một gói và do đó loại của chúng sẽ bằng nhau:

fmt.Println(t == t2) // Prints true

Lưu ý: Tôi xem xét điều này lỗ hổng: nếu điều này được cho phép bằng cách sử dụng sự phản chiếu, thì điều này có thể được thực hiện mà không cần sử dụng sự phản chiếu. Nếu không có phản ánh thì lỗi biên dịch là hợp lý, thì việc sử dụng sự phản chiếu sẽ dẫn đến hoảng loạn thời gian chạy. Tôi đã mở một vấn đề cho điều này, bạn có thể làm theo nó ở đây: số # 16616. Fix hiện đang hướng tới Go 1.8.


8
2017-08-05 09:33



Thông tin tốt đẹp! Cảm ơn! - Siu Ching Pong -Asuka Kenji-


Oh! Tôi chỉ nhận ra rằng các tên trường là chữ thường, và do đó không công khai! Thay đổi chữ cái đầu tiên của tên trường thành chữ hoa sẽ giải quyết được vấn đề:

package main

import (
    "temp"
)

func main() {
    value := struct {
        I int
        S string
    }{
        0, "Hello, world!",
    }
    temp.PrintAnonymous(value)
    temp.PrintAnonymous2(struct{}{})
}
package temp

import (
    "fmt"
)

func PrintAnonymous(v struct{I int; S string}) {
    fmt.Printf("%d: %s\n", v.I, v.S)
}

func PrintAnonymous2(v struct{}) {
    fmt.Println("Whatever")
}

4
2017-08-05 08:55