0

I am learning Go and decided to rewrite a MQTT orchestrator which I originally wrote in Python. The very basic part works fine:

package main

import (
    "fmt"
    "time"
    "os"

    MQTT "github.com/eclipse/paho.mqtt.golang"
    log "github.com/sirupsen/logrus"
)

// definitions for a switch

type Switch struct {
    topic string
    state int
}

func allEvents(client MQTT.Client, msg MQTT.Message) {
    log.WithFields(log.Fields{"topic": msg.Topic(), "payload": fmt.Sprintf("%s", msg.Payload())}).Info()
}


func initMQTT() MQTT.Client {
    opts := MQTT.NewClientOptions()
    opts.AddBroker("tcp://mqtt.example.com:1883")
    opts.SetClientID("go-dispatcher")
    opts.SetCleanSession(true)
    client := MQTT.NewClient(opts)
    if token := client.Connect(); token.Wait() && token.Error() != nil {
        panic(token.Error())
    }
    log.Info("connected to MQTT broker")
    return client
}

func main() {
    // this is that part I want to modify later in the question
    c := initMQTT()
    if token := c.Subscribe("#", 0, allEvents); token.Wait() && token.Error() != nil {
        fmt.Println(token.Error())
        os.Exit(1)
    }

    time.Sleep(100000 * time.Hour)
}

Having used pointers in a distant past with C, I wanted to modify the program to pass, in the initialization part, the client by reference (more as a learning experience, the first code looks better to me)

package main

import (
    "fmt"
    "time"
    "os"

    MQTT "github.com/eclipse/paho.mqtt.golang"
    log "github.com/sirupsen/logrus"
)

// definitions for a switch

type Switch struct {
    topic string
    state int
}

func allEvents(client MQTT.Client, msg MQTT.Message) {
    log.WithFields(log.Fields{"topic": msg.Topic(), "payload": fmt.Sprintf("%s", msg.Payload())}).Info()
}

// this time we get a pointer and do not return anything
func initMQTT(client *MQTT.Client) {
    opts := MQTT.NewClientOptions()
    opts.AddBroker("tcp://mqtt.example.com:1883")
    opts.SetClientID("go-dispatcher")
    opts.SetCleanSession(true)
    client = MQTT.NewClient(opts)
    if token := *client.Connect(); token.Wait() && token.Error() != nil {
        panic(token.Error())
    }
    log.Info("connected to MQTT broker")
}

func main() {
    // the client is defined in main()
    var c MQTT.Client
    // and passed by reference so that the function modifies it
    initMQTT(&c)
    if token := c.Subscribe("#", 0, allEvents); token.Wait() && token.Error() != nil {
        fmt.Println(token.Error())
        os.Exit(1)
    }

    time.Sleep(100000 * time.Hour)
}

It fails to compile with

# command-line-arguments
.\main.go:29:9: cannot use mqtt.NewClient(opts) (type mqtt.Client) as type *mqtt.Client in assignment:
    *mqtt.Client is pointer to interface, not interface
.\main.go:30:21: client.Connect undefined (type *mqtt.Client is pointer to interface, not interface)

Following advice form another question, I tried to remove & and * (blindly to start with, to tell the truth), and get a runtime error

time="2018-05-26T21:02:20+02:00" level=info msg="connected to MQTT broker"
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x604486]

goroutine 1 [running]:
main.main()
    D:/Seafile/dev/Go/src/perso/domotique.dispatcher/main.go:39 +0x36

I though that since I defined c, I could just pass it as a reference but apparently the C-way is not the right one here? (it is usually in the examples I read)

WoJ
  • 27,165
  • 48
  • 180
  • 345
  • As the error says `mqtt.Client` is not `*mqtt.Client` and you cannot use them interchangeably. Your init takes a `*mqtt.Client` but [`mqtt.NewClient`](https://godoc.org/github.com/eclipse/paho.mqtt.golang#NewClient) returns `mqtt.Client`, therefore this line `client = MQTT.NewClient(opts)` is illegal. – mkopriva May 26 '18 at 20:47
  • Secondly [`mqtt.Client`](https://godoc.org/github.com/eclipse/paho.mqtt.golang#Client) is an `interface` type and in Go it almost never a good idea to use pointers to interface types. So to practice pointers in Go you will better off doing it with normal types, like structs, strings, ints, etc. Not interfaces, also maps aren't good candidates.. – mkopriva May 26 '18 at 20:51
  • If you decide to stick with interface pointers anyways, please understand that a pointer value to an interface type cannot be used directly to call the methods of the interface (https://play.golang.org/p/AjEi65n4sef) but that you already know since you are attempting to dereference the pointer on this line `*client.Connect()`. This however is not how it's done due to evaluation precedence rules which are ambiguous in your expression, to make them clear for the compiler you can use parentheses like so [`(*client).Connect()`](https://play.golang.org/p/TERzSqwk0NB) – mkopriva May 26 '18 at 20:57
  • And, I forgot, to fix the initialization line you need to dereference the pointer of the client argument and assign the result of NewClient to that. `*client = MQTT.NewClient(opts)`... So really you have two lines that need fixing to make the program compile: https://play.golang.org/p/1sY7bHNBw22 – mkopriva May 26 '18 at 21:02
  • You must forget about "reference" now and forever. There is absolutely no such thing in Go and passing a pointer value is not pass by reference. Get over that and treat pointers as what they are: A memory address. – Volker May 27 '18 at 14:18

1 Answers1

0

This is a common misunderstanding. Because you've defined c as a value rather than a pointer type, you can't modify it without re-defining it.

The fact that you've passed its memory address does not enable you to modify it. This is different from a number of other languages.

Venantius
  • 2,471
  • 2
  • 28
  • 36
  • 1
    *"The fact that you've passed its memory address does not enable you to modify it."* That's not correct, see here: https://play.golang.org/p/l5-o5a3-QZf – mkopriva May 26 '18 at 21:07