PowerShellPracticeAndStyle icon indicating copy to clipboard operation
PowerShellPracticeAndStyle copied to clipboard

Write-Output vs. Return vs. $Output

Open Jaykul opened this issue 10 years ago • 35 comments
trafficstars

There is currently a recommendation in Function and Structure to not use "return" but it says to "just put the variable on a line by itself" ...

I wonder if that's the majority opinion?

Assuming this is the last line of my function, what's better:

$output = $temp + (Get-Thing $temp)
return $output
$output = $temp + (Get-Thing $temp)
$output
$output = $temp + (Get-Thing $temp)
Write-Output $output

I won't go into piping output to Write-Output ;-)

Jaykul avatar Aug 14 '15 02:08 Jaykul

I try to use Write-Output when logical.

mwjcomputing avatar Aug 14 '15 13:08 mwjcomputing

Personally, I tend to use the return statement in functions where I only intend to output a single object (or at least only output from that line.) I know that the engine doesn't guarantee that, but it's just my habit, and it makes the code easier for me to read later on.

In exported, cmdlet-style functions, I tend to just put commands or variables on their own line and let it send stuff to the output stream. (This is mostly done in a Process block and inside a foreach loop, where multiple objects can wind up output.)

dlwyatt avatar Aug 14 '15 15:08 dlwyatt

I never use return except to return early and I never use Write-Output I just specify the variable on a line by itself like the OP's middle example. I'm not too opinionated on whether or not to specify output on the same line as the return in the early return case.

Personally I wish the language would have never allowed you to specify an expression as part of a return statement. I think that would have eliminated some confusion over how PowerShell functions work. But that ship has sailed.

rkeithhill avatar Aug 14 '15 15:08 rkeithhill

I never use return or Write-Output. I tend to just let the engine output whatever isn't captured. And whenever possible I try to stream objects out one at a time - that's an important point when writing pipeline capable functions.

mattmcnabb avatar Aug 15 '15 15:08 mattmcnabb

Ditto.

I would use return religiously if it was contractual, but since it is not I don't use it.

IMHO, both return and Write-Output give false expectations to someone less familiar to PowerShell.

-----Original Message----- From: "mattmcnabb" [email protected] Sent: ‎2015-‎08-‎15 12:08 PM To: "PoshCode/PowerShellPracticeAndStyle" [email protected] Subject: Re: [PowerShellPracticeAndStyle] Write-Output vs. Return vs. $Output(#46)

I never use return or Write-Output. I tend to just let the engine output whatever isn't captured. And whenever possible I try to stream objects out one at a time - that's an important point when writing pipeline capable functions. — Reply to this email directly or view it on GitHub.

KirkMunro avatar Aug 15 '15 16:08 KirkMunro

I like Write-Output. I have my own rule of always explicitly sending an object to a stream. It makes it easier to understand at a glance in the future.

Return should be contractual, but it's not. It's useful for returning early, but other than that I don't think it should be used.

Letting objects fall out of the pipe is probably most familiar to shell users, and doesn't carry the implicit expectation that it ends program output, but to me it's less clear.

Ultimately I think Write-Output is clearer and better to someone familiar with PowerShell, and I think we should ultimately be writing for them.

bryanwinstead avatar Jan 04 '16 21:01 bryanwinstead

Some thoughts.

In theory, or in rare practical cases, a user may redefine Write-Output for some reasons, i.e. debugging, logging, or even accidentally. This may affect the code which uses Write-Output naked, without specifying its full name with its original module. This may or may not be what a user wants.

Note that user aliases and functions have higher precedence than cmdlets, see help about_Command_Precedence.

On the other hand, a user cannot alter explicit return or implicit output statements.

So naked Write-Output is somewhat more flexible and at the same time less safe.

Write-Output performance is probably worse than return or output. If a function is called from a loop this may be an issue.

nightroman avatar Jan 05 '16 07:01 nightroman

From a few experiments, it appears to me that using Write-Output results in worse perf but like you say, that's probably only a concern when executed from a loop with lots of iterations.

BTW I agree with @KirkMunro that using Write-Output sets up false expectations that that is the only way to write output which simply isn't true.

Also, would you write code like this:

for ($i=0; $i -lt 10000; $i++) { $sum += Write-Output $i }

I wouldn't but technically you are writing $i to the output of the pipeline that's on the RHS of the assignment statement.

rkeithhill avatar Jan 07 '16 02:01 rkeithhill

Why should we not use return or write-output, cannot find that information. Does it not return to the output stream / pipeline? I have never experienced a problem with return, but i almost never pipe my functions to something else. Thanks

maekee avatar Jul 29 '16 05:07 maekee

@maekee to sum up this long thread:

In other programming languages with a return statement, the return statement is contractual: nothing returns output from a function except the return statement. Thus, for people with experience in other languages, the presence of code like return $output in PowerShell is misleading. They think: "oh, ok, PowerShell has a return statement" and don't realize that any un-captured result values get output also -- and don't even realize they can output multiple times.

The reasoning for, and objection to, Write-Output is similar. People like it because:

... always explicitly sending an object to a stream... makes it easier to understand [the code] at a glance in the future.

The problem is that this can lead to a false sense of security. If you see Write-Output you assume that output can only come from lines with Write-Output, when in reality it can come from any command or method call. Or at least, any that doesn't start with an assignment (or [void] cast), or end with out-null ...

So while some people like it because it highlights the spots where you intentionally output something -- other people argue it's presence distracts you from the fact that other lines could output.

Therefore

Since return cannot always be used (sometimes you need to write output more than once), you should prefer Write-Output and use return only for ending execution. But since Write-Output can lead to false expectations and in addition is slightly slower, (and worse, can be overwritten by functions or aliases) ...

We recommend:

Use return only for ending execution.

Avoid Write-Output. Instead, when you want to make output clearer, just assign output to a relevantly named variable. and put that a variable on a line by itself to signal explicit output. In particular, avoid Write-Output -NoEnumerate which is broken in PowerShell 6.

Jaykul avatar Aug 01 '16 22:08 Jaykul

Thanks for taking your time to explain. So if i want to end execution, i only write return and nothing more?

/Micke

2 aug. 2016 kl. 00:30 skrev Joel Bennett [email protected]:

@maekee to sum up this long thread:

In other programming languages with a return statement, the return statement is contractual: nothing returns output from a function except the return statement. Thus, for people with experience in other languages, the presence of code like return $output in PowerShell is misleading. They think: "oh, ok, PowerShell has a return statement" and don't realize that any un-captured result values get output also -- and don't even realize they can output multiple times.

The reasoning for, and objection to, Write-Output is similar. People like it because:

... always explicitly sending an object to a stream... makes it easier to understand [the code] at a glance in the future.

The problem is that this can lead to a false sense of security. If you see Write-Output you assume that output can only come from lines with Write-Output, when in reality it can come from any command or method call. Or at least, any that doesn't start with an assignment (or [void] cast), or end with out-null ...

So while some people like it because it highlights the spots where you intentionally output something -- other people argue it's presence distracts you from the fact that other lines could output.

Therefore

Since return cannot always be used (sometimes you need to write output more than once), you should prefer Write-Output and use return only for ending execution. But since Write-Output can lead to false expectations and in addition is slightly slower, (and worse, can be overwritten by functions or aliases) ...

We recommend:

Use return only for ending execution.

Avoid Write-Output (although you may want to use for it's -NoEnumerate switch). Instead, when you want to make output clearer, just assign output to a relevantly named variable. and put that a variable on a line by itself to signal explicit output.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

maekee avatar Aug 02 '16 08:08 maekee

@maekee, in PowerShell the flow control keywords of break, continue, and return should really only be used to control execution.

Continue moves to the next item in a loop and stops processing the current one.

Break pops us out of the loop entirely.

Return stops the current block (begin/process/end) block execution and returns to the caller (or the prompt).

1RedOne avatar Aug 02 '16 12:08 1RedOne

Awesome, thanks. So i van use Return without any "parameter"? Or is break better for that?

/Micke

2 aug. 2016 kl. 14:31 skrev Stephen Owen [email protected]:

@maekee, in PowerShell the flow control keywords of break, continue, and return should really only be used to control execution.

Continue moves to the next item in a loop and stops processing the current one.

Break pops us out of the loop entirely.

Return stops all execution and dumps us back to the console.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

maekee avatar Aug 02 '16 12:08 maekee

You can't (shouldn't) use break or continue outside of a loop construct like foreach/for/do/while/switch -- they are for loops exclusively.

Jaykul avatar Aug 03 '16 18:08 Jaykul

This discussion was just hitting its stride and ended. Was there ever a final (agreeable) verdict or consensus?

Skatterbrainz avatar Aug 24 '18 12:08 Skatterbrainz

See my summary with it's 8 up-votes?

We just need to capture this in the docs and close it.

Jaykul avatar Aug 24 '18 15:08 Jaykul

TIL Write-Output -NoEnumerate was broken on PowerShell 6 for 16 months or longer. In summary:

DO NOT USE Write-Output -- EVER.

Jaykul avatar Mar 04 '19 22:03 Jaykul

@Jaykul : you might want to edit https://github.com/PoshCode/PowerShellPracticeAndStyle/issues/46#issuecomment-236727676 and stripe out the -NoEnumerate bit there.

jpluimers avatar Mar 05 '19 10:03 jpluimers

Ugh. What I should do, is copy that into the book somewhere and close this thread. It's always next weekend...

Jaykul avatar Mar 08 '19 05:03 Jaykul

This times 1000x.

jzabroski avatar Apr 12 '19 20:04 jzabroski

The Write-Output -NoEnumerate issue is fixed in PS6.2.0 and up. 🎉

vexx32 avatar Jun 28 '19 20:06 vexx32

@vexx32 Write-Output -NoEnumerate is not fully fixed in PS6.2.0 and up:

See: https://github.com/PowerShell/PowerShell/issues/5122

PS C:\Users\john.zabroski> ($PSVersionTable).PSVersion

Major  Minor  Patch  PreReleaseLabel BuildLabel
-----  -----  -----  --------------- ----------
6      2      1

PS C:\Users\john.zabroski> (Write-Output -NoEnumerate 1).GetType().FullName
System.Collections.Generic.List`1[[System.Object, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]

PS C:\Users\john.zabroski> (1 | Write-Output -NoEnumerate).GetType().FullName
System.Int32

and also:

PS C:\Users\john.zabroski> (Write-Output -NoEnumerate @()).GetType().FullName
System.Object[]

The problem still exists for:

  • Direct argument
  • Not a collection

The problem is fixed for:

  • Pipeline argument
  • Collection or scalar value

jzabroski avatar Jun 28 '19 20:06 jzabroski

Interesting. Mind xposting that to the PS repo? We should fix that too ^^

Though I don't see anything wrong with that last one?

vexx32 avatar Jun 28 '19 20:06 vexx32

The last one is correct. Just provided for completeness.

jzabroski avatar Jun 28 '19 20:06 jzabroski

It seems astonishing to me as a C coder that you can't just say 'return $something' and have $something returned.

I've been brushing up on PS recently and decided to use it to write a fairly complex custom backup system with numerous functions returning objects and values, and I find myself in a continuous miasma of 'Why is that value appearing in the terminal?' and 'Why is that being returned?', and have been through various iterations of using return, then using Write-Output, then just having the variable there with a return statement on the next line, and all seem to be very much doing what they fancy rather than what I want them to.

This idea of just plonking a variable name in with no assignment and it is left for something else to pick up is I guess great for pipelining but IMO it makes PS too flaky to use for anything more than little utility scripts - which is fair enough I guess, that's what PS is for and if I wasn't using this exercise to learn what's new and changed in PS I'd have written it in a more suitable language, but it really puts me off using PS for those tasks where you want a bit of complexity but want to keep things fairly simple, and a scripting language where you can get at .Net is a great concept.

Could there not be a 'Plonk' statement where you say 'Plonk $something' and it does the same as this curious lonely '$something' statement, with backwards compatibility so the lonely variable still worked but at least an explicit 'Plonk' would be clear. Of course, it's clear to experienced PS users, but it makes it rather frustrating for even a seasoned programmer of C or C# or the like to get something vaguely complex working.

Surely there can be an implementation of 'return $something' that does what programmers think it does.

Maybe with some pragmas or flags or whatever to make the intention explicit but the net seems to be full of confusion about something so basic as how to get a function to just return an object or variable to the caller, which surely can't be healthy for a language !

I vaguely considered just having a global $TheThingThatWasReturned and making all functions use that, it's single-threaded and only for home use so what the hell, it's a code smell as strong as a vat of last year's Stilton but at least I'd know what was going on :)

silentdiverchris avatar Jul 27 '21 07:07 silentdiverchris

Miasma. Perfect description

jzabroski avatar Jul 27 '21 11:07 jzabroski

Could there not be a 'Plonk' statement where you say 'Plonk $something' and it does the same as this curious lonely '$something' statement, with backwards compatibility so the lonely variable still worked but at least an explicit 'Plonk' would be clear. Of course, it's clear to experienced PS users, but it makes it rather frustrating for even a seasoned programmer of C or C# or the like to get something vaguely complex working.

Write-Output does this.

vexx32 avatar Jul 27 '21 12:07 vexx32

Write-Output does this.

Write-Output is broken across PowerShell versions, which is the whole point of this issue.

@silentdiverchris In my experience, PowerShell is the epitome of Blub and PowerShell users literally don't feel or see the pain you or I experience when using it. It's best to point out things that don't actually work properly, like Write-Output -NoEnumerate, because otherwise it becomes a religious discussion and gets nowhere.

jzabroski avatar Jul 27 '21 12:07 jzabroski

That does work now, though, unless you're adamant about using an already out-of-support version of pwsh. I don't think any versions still in support have that bug.

Also, using -NoEnumerate at all is a bit of an edge case. Passing whole collections down the pipeline is generally inadvisable, PowerShell doesn't handle it that well anyway.

vexx32 avatar Jul 27 '21 12:07 vexx32

unless you're adamant about using an already out-of-support version of pwsh.

Our IT department uses PowerShell 5.0 for a lot of things, in part because of third party dependencies and its a small team.

Anyway, Write-Output -NoEnumerate is just an example of documenting that something doesnt work correctly, and staying out of religious discussions like 'C does this differently and C's approach seems better for complex, systems programming'. I will say, if you are writing a backup utility, @silentdiverchris , you should use C# and use the open source VSS library available on nuget.org - but that also requires becoming very familiar with admining VSS

jzabroski avatar Jul 27 '21 14:07 jzabroski