Могу ли я создать функцию, которую можно использовать только с отсрочкой?

Например:

package package

// Dear user, CleanUp must only be used with defer: defer CleanUp()
func CleanUp() {
    // some logic to check if call was deferred
    // do tear down
}

И в пользовательском коде:

func main() {
    package.CleanUp() // PANIC, CleanUp must be deferred!
}

Но все должно быть хорошо, если пользователь запускает:

func main() {
   defer package.CleanUp() // good job, no panic
}

Вещи, которые я уже пробовал:

func DeferCleanUp() {
    defer func() { /* do tear down */ }()
    // But then I realized this was exactly the opposite of what I needed
    // user doesn't need to call defer CleanUp anymore but...
}
// now if the APi is misused it can cause problems too:
defer DeferCleanUp() // a defer inception xD, question remains.

person marcio    schedule 24.12.2014    source источник
comment
Даже если бы вы могли реализовать такую ​​вещь, кто-то мог бы просто создать функцию, единственной строкой которой была defer package.CleanUp(), а затем вызвать эту функцию, фактически вызвав вашу функцию без defer (но на самом деле с defer). ).   -  person icktoofay    schedule 24.12.2014
comment
Вы имеете в виду defer func(){ defer CleanUp() }? Да, Санта ненавидит таких пользователей :)   -  person marcio    schedule 24.12.2014
comment
Нет, больше похоже на func() { defer CleanUp() }() без defer перед внешней функцией.   -  person icktoofay    schedule 24.12.2014
comment
Это то, что я хочу, как и на func main(){ defer CleanUp() }. дыра заключается в том, чтобы обязать использовать функцию с defer.   -  person marcio    schedule 24.12.2014
comment
Я знаю, но я говорю, что они могут встроить это в другую функцию, например. func main() { foo(); func() { defer CleanUp() }(); bar() }, который, вероятно, будет вести себя точно так же, как func main() { foo(); CleanUp(); bar() }, но все же будет соответствовать вашим ограничениям.   -  person icktoofay    schedule 24.12.2014
comment
Можете ли вы сказать больше, почему вы хотите это сделать? Возможно, другой подход может удовлетворить ту же цель?   -  person dyoo    schedule 24.12.2014
comment
Единственное, чего я пытаюсь избежать, это чтобы кто-то вызывал CleanUp() без отсрочки по отвлечению внимания, чтобы приложение немедленно запаниковало. Где он называется, не имеет большого значения.   -  person marcio    schedule 24.12.2014
comment
Другими словами, я хотел бы идиоматический способ сказать, что функция полезна только тогда, когда она отложена.   -  person marcio    schedule 24.12.2014
comment
@marcio - я не думаю, что есть один. mutex.Unlock() очень, очень часто откладывается сразу после соответствующего mutex.Lock(), но у него нет особого статуса отсрочки. Иногда нужно просто документировать.   -  person twotwotwo    schedule 24.12.2014
comment
@twotwotwo это грустно, я попытаюсь попросить эту функцию у разработчиков Go, это Рождество;)   -  person marcio    schedule 24.12.2014
comment
Сомневаюсь, что кабале понравится эта идея.   -  person Not_a_Golfer    schedule 24.12.2014
comment
@marcio Есть своего рода уродливый способ сделать это, но он действительно дерьмовый, и я не пишу это как ответ: если вы проверите стек вызовов, вы увидите, что при вызове deferred на один уровень выше вашей функции линия вызывающего абонента is }, что означает, что функция вот-вот выйдет из функции, а ваша функция не была вызвана напрямую. В противном случае вы увидите прямой вызов вашей функции на этом уровне.   -  person Not_a_Golfer    schedule 24.12.2014
comment
Мне очень любопытно это увидеть, как проверить стек? :)   -  person marcio    schedule 24.12.2014
comment
@marcio Я опубликую это как ответ, надеюсь, за меня не проголосуют :)   -  person Not_a_Golfer    schedule 24.12.2014
comment
не волнуйтесь, эта тема очень экспериментальная, суслики будут себя вести.   -  person marcio    schedule 24.12.2014


Ответы (1)


Хорошо, по запросу ОП и просто для смеха я публикую этот хакерский подход к решению этой проблемы, просматривая стек вызовов и применяя некоторые эвристики.

ВНИМАНИЕ: не используйте это в реальном коде. Я не думаю, что проверка отложенного — это даже хорошо. Так что, пожалуйста, не минусуйте :)

Также обратите внимание: этот подход будет работать только в том случае, если исполняемый файл и исходный код находятся на одном компьютере.

Ссылка на суть: https://gist.github.com/dvirsky/dfdfd4066c70e8391dc5 (это не не работает на игровой площадке, потому что вы не можете прочитать исходный файл там)

package main

import(
    "fmt"
    "runtime"
    "io/ioutil"
    "bytes"
    "strings"
)




func isDeferred() bool {

    // Let's get the caller's name first
    var caller string
    if fn, _, _, ok  := runtime.Caller(1); ok {
        caller = function(fn)
    } else {
        panic("No caller")
    }

    // Let's peek 2 levels above this - the first level is this function,
    // The second is CleanUp()
    // The one we want is who called CleanUp()
    if _, file, line, ok  := runtime.Caller(2); ok {

        // now we actually need to read the source file
        // This should be cached of course to avoid terrible performance
        // I copied this from runtime/debug, so it's a legitimate thing to do :)
        data, err := ioutil.ReadFile(file)
        if err != nil {
            panic("Could not read file")
        }

        // now let's read the exact line of the caller 
        lines := bytes.Split(data, []byte{'\n'})
        lineText := strings.TrimSpace(string(lines[line-1]))
        fmt.Printf("Line text: '%s'\n", lineText)


        // Now let's apply some ugly rules of thumb. This is the fragile part
        // It can be improved with regex or actual AST parsing, but dude...
        return lineText == "}" ||  // on simple defer this is what we get
               !strings.Contains(lineText, caller)  || // this handles the case of defer func() { CleanUp() }()
               strings.Contains(lineText, "defer ")


    } // not ok - means we were not clled from at least 3 levels deep

    return false
}

func CleanUp() {
    if !isDeferred() {
        panic("Not Deferred!")
    }


}

// This should not panic
func fine() {
    defer CleanUp() 

    fmt.Println("Fine!")
}


// this should not panic as well
func alsoFine() {
    defer func() { CleanUp() }()

    fmt.Println("Also Fine!")
}

// this should panic
func notFine() {
    CleanUp() 

    fmt.Println("Not Fine!")
}

// Taken from the std lib's runtime/debug:
// function returns, if possible, the name of the function containing the PC.
func function(pc uintptr) string {
    fn := runtime.FuncForPC(pc)
    if fn == nil {
        return ""
    }
    name := fn.Name()
    if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
        name = name[lastslash+1:]
    }
    if period := strings.Index(name, "."); period >= 0 {
        name = name[period+1:]
    }
    name = strings.Replace(name, "·", ".", -1)
    return name
}

func main(){
    fine()
    alsoFine()
    notFine()
}
person Not_a_Golfer    schedule 24.12.2014
comment
Спасибо и вау... это самая хакерская штука с голангом. Я думал, вы каким-то образом получите доступ к стеку отсрочки, а не к фактическому исходному коду x) - person marcio; 24.12.2014
comment
@марсио спасибо. Если вы пойдете дальше и прочитаете исходный код runtime и runtime/debug, все будет выглядеть примерно так. Они прячут уродство под капотом. Это, например, источник функции, которая печатает стеки для паники и т. д.: golang.org/src/runtime/debug/stack.go?s=516:533#L40 - person Not_a_Golfer; 24.12.2014
comment
Спасибо за реф. Возможно, этот другой ответ может помочь улучшить эту реализацию stackoverflow.com/a/18454768/438563 стек отсрочки и найдите вызов fn. Если его нет, то он не отложен. - person marcio; 24.12.2014
comment
@marcio вау, это глубоко. Интересно, работает ли он до сих пор, учитывая, что среда выполнения была переписана в основном на Go в версии 1.4. Но нет времени проверять это, у меня есть реальный код для написания :) - person Not_a_Golfer; 24.12.2014