{"id":2584,"date":"2017-03-16T16:35:05","date_gmt":"2017-03-16T04:35:05","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=2584"},"modified":"2017-03-16T19:42:42","modified_gmt":"2017-03-16T07:42:42","slug":"anatomy-of-a-utility-function-booltostr","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/2584\/","title":{"rendered":"Anatomy of a utility function:  BoolToStr()"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">5<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span><p><a href=\"https:\/\/www.facebook.com\/groups\/137012246341854\/permalink\/1386109158098817\/\">Jaap Van Goor was asking on FaceBook about some seemingly strange behaviour when obtaining string representations of Booleans using the Delphi RTL<\/a>, which led me to revisit some familiar (and some not so familiar) old Delphi ground and take a look at the area involved in further detail.<\/p>\n<p><!--more--><\/p>\n<p>First of all, let&#8217;s quickly deal with the initial &#8220;problem&#8221;.<\/p>\n<p>Jaap was concerned to understand why <strong>ABoolean.ToString<\/strong> returned <em>0<\/em> or <em>-1<\/em> rather than <strong>false<\/strong> and <strong>true<\/strong> (respectively).<\/p>\n<p>The answer is that the <strong>Boolean<\/strong> type is not <em>really<\/em> an <em>enum<\/em> (though RTTI identifies it as such) but a much more specialised type that has some things in common with an enum but also some quite significant differences.<\/p>\n<p>The symbols <strong>true<\/strong> and <strong>false<\/strong> are effectively compiler defined constants but with some additional caveats about what one of those particular constants actually <em>means<\/em>.  That is &#8220;True&#8221; is both a value (1) but also the result of an <em>evaluation<\/em> of a value, where any non-zero value is &#8220;true&#8221;.<\/p>\n<p>Unless I&#8217;m mistaken, at time of writing it&#8217;s still not possible to use <strong>ToString<\/strong> on a variable of type enum, where-as you can on an <strong>Integer<\/strong>.  Which is one clue that <strong>Boolean<\/strong>&#8216;s aren&#8217;t quite the enums they might claim or appear to be.<\/p>\n<h2> Friendly Advice <\/h2>\n<p>A couple of suggestions were made in response to Jaap&#8217;s post as to alternative ways to convert a <strong>Boolean<\/strong> into a more meaningful string representation.  One of these was a function called <strong>BoolToStr()<\/strong>.<\/p>\n<p>Now, on the occasions when I&#8217;ve needed something similar, the needs are usually quite specific to the context of a particular problem.  Sometimes you do want simply <strong>true<\/strong> and <strong>false<\/strong>, other times you might want <strong>yes<\/strong>\/<strong>no<\/strong> and on yet others <strong>on<\/strong>\/<strong>off<\/strong>, <strong>enabled<\/strong>\/<strong>disabled<\/strong> etc<\/p>\n<p>And of course you may want to localise these values.<\/p>\n<p>As a result I have always used a simple Boolean-indexed array for this myself, allowing me to specify the particular values applicable in a particular context:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n   const\r\n     BOOLSTR: array[false..true] of String = ('false', 'true'); \r\n   begin\r\n     \/\/ ..\r\n\r\n     s := BOOLSTR[aBoolean];\r\n\r\n     \/\/ ..\r\n   end;   \r\n<\/pre>\n<p>If the particular context has some wider scope than a specific block of code, then this of course can itself be wrapped up inside a helper method of my own, itself scoped as appropriate to the context.<\/p>\n<p>But I didn&#8217;t even recall ever having heard of <strong>BoolToStr()<\/strong> before, so I took a look.<\/p>\n<p>As a result I&#8217;m wondering now whether I <em>had<\/em> in fact heard of it, looked into it <em>and then deliberately purged it from my memory<\/em>!<\/p>\n<p>Brace yourselves, this isn&#8217;t going to be pretty.<\/p>\n<h2> Here be Dragons <\/h2>\n<p><strong>BoolToStr()<\/strong> accepts up to <em>two<\/em> boolean parameters.  The first is the one you wish to convert to a string representation and the second indicates whether you want to &#8220;<em>UseBoolStrs<\/em>&#8220;.  If you indicate that you do not (the default if not specified) then you get <strong>0<\/strong> or <strong>-1<\/strong>:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\nfunction BoolToStr(B: Boolean; UseBoolStrs: Boolean = False): string;\r\nconst\r\n  cSimpleBoolStrs: array [boolean] of String = ('0', '-1');\r\nbegin\r\n  if UseBoolStrs then\r\n  begin\r\n    VerifyBoolStrArray;\r\n    if B then\r\n      Result := TrueBoolStrs[0]\r\n    else\r\n      Result := FalseBoolStrs[0];\r\n  end\r\n  else\r\n    Result := cSimpleBoolStrs[B];\r\nend;\r\n<\/pre>\n<p>So far so simple.  Well, down that particular conditional branch anyway.  If <strong>UseBoolStrs<\/strong> is <em>false<\/em> then the result is simply plucked from a Boolean-indexed array.<\/p>\n<p>Where things get interesting and &#8211; frankly &#8211; messy and not a little bit scary, is if you have said that you do indeed wish to <strong>UseBoolStrs<\/strong>.<\/p>\n<p>In that case then the RTL goes scurrying off down a path that takes us through <strong>VerifyBoolStrArray<\/strong> (every time) and then returning the first value in one of two separate arrays, conditional on whether we&#8217;re converting a true or false value.<\/p>\n<p><strong>VerifyBoolStrArray<\/strong> itself simply checks whether the length of these two arrays is not zero (i.e. empty) and if either one is then sets an length of 1 with an initial, non-localized default value in each (the constants <em>DefaultTrueBoolStr<\/em> and <em>DefaultFalseBoolStr<\/em> are defined as &#8216;True&#8217; and &#8216;False&#8217;).<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\nprocedure VerifyBoolStrArray;\r\nbegin\r\n  if Length(TrueBoolStrs) = 0 then\r\n  begin\r\n    SetLength(TrueBoolStrs, 1);\r\n    TrueBoolStrs[0] := DefaultTrueBoolStr;\r\n  end;\r\n  if Length(FalseBoolStrs) = 0 then\r\n  begin\r\n    SetLength(FalseBoolStrs, 1);\r\n    FalseBoolStrs[0] := DefaultFalseBoolStr;\r\n  end;\r\nend;\r\n<\/pre>\n<p>Also of some interest is that in the <strong>TryStrToBool()<\/strong> function the implementation allows for the possibility that these arrays may have more than just the one entry introduced by the <strong>Verify<\/strong> function.<\/p>\n<p><em>But nowhere else in the entire RTL does any code modify or add to these arrays<\/em>.<\/p>\n<p>Nothing adds to them, nothing changes them.  Nothing.<\/p>\n<p>And the strings placed in the arrays are just regular string constants, not <strong>resourcestring<\/strong>, and are specifically commented as not to be localised.<\/p>\n<p>On the face of it then, it is all just unnecessarily convoluted and could simply be replaced by a simple lookup from a similarly boolean-indexed array:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\nfunction BoolToStr(B: Boolean; UseSymbolicStrs: Boolean = False): string;\r\nconst\r\n  cStrs: array [boolean, boolean] of String = (\r\n                                               (    '0',   '-1'),\r\n                                               ('False', 'True')\r\n                                              );\r\nbegin\r\n  result := cStrs[UseSymbolicStrs][B];\r\nend;\r\n<\/pre>\n<p>(Ok, so I admit to a little additional finesse here.  :))<\/p>\n<p>But it bothered me.<\/p>\n<p>Why go to all this trouble if the arrays are only ever going to be setup in a particular way&#8230; ?<\/p>\n<p>Which led to a worrying thought and the realisation of the true horror lurking within <strong>BoolToStr()<\/strong>.  The reason for consulting these arrays rather than simply directly using the constants held within them, as well as for allowing that even the length of the arrays might vary&#8230;.<\/p>\n<p>They are <strong>Unit Interface Variables<\/strong>.<\/p>\n<p>Or as some people like to call them:  <strong><em>Global<\/em> Variables<\/strong>.<\/p>\n<p>Anybody is free to add extra items to those arrays or change the values of any strings that might already be there.<\/p>\n<p>Fancy messing with a colleague&#8217;s Friday afternoon ?  Simply check-in this code somewhere in the <strong>initialization<\/strong> of a unit:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\ninitialization\r\n  if not assigned(TrueBoolStrs) then\r\n    SetLength(TrueBoolStrs, 1);\r\n\r\n  if not assigned(FalseBoolStrs) then\r\n    SetLength(FalseBoolStrs, 1);\r\n\r\n  TrueBoolStrs[0]  := 'False';\r\n  FalseBoolStrs[0] := 'True';\r\n<\/pre>\n<p>Then sit back and wait for the hilarity to ensue.  (Of course your code review process will spoil your fun, but you get the point).  \ud83d\ude42<\/p>\n<p>More seriously, this of course (we must presume) to allow you to come up with your own alternative replacement strings for <strong>True<\/strong> and <strong>False<\/strong> for conversions.  Alternatives which you wish to use <em>across the entirety of your application, in every nook and cranny where such conversions take place<\/em>.<\/p>\n<p>You can even define multiple strings <em>from<\/em> which Booleans can then be converted (by <strong>StrToBool<\/strong> and <strong>TryStrToBool<\/strong>), although only the <em>first<\/em> one in each array will be used when converting back the other way, as we have seen.<\/p>\n<p>Of course, to do that you need to check to see whether any RTL code might have already initialised those arrays and then just change or add the values you need, or you may need to initialise the arrays entirely, if the RTL has not yet initialised them.<\/p>\n<p>That <strong>VerifyBoolStrArrays<\/strong>() function is the one implementation detail that is private to the <strong>SysUtils<\/strong> function though unfortunately, so you&#8217;ll have to do all that the long way around.<\/p>\n<p>These complaints aside, initially this might seem quite useful, until you realise that this means that if you rely on this yourself, then you must also always be aware that every time you use some 3rd party library or framework there is a chance &#8211; however slim &#8211; that they too thought it would be a great idea to set up their own custom strings in those arrays and that their idea of what makes for really useful strings isn&#8217;t the same as yours.<\/p>\n<p>So not only is this convenient looking little utility method horrendously inefficient, all that overhead to make it possible necessarily involves exposing any consumer code to potential risk of side effects arising from sharing code.<\/p>\n<p>Yikes.<\/p>\n<h2> Retrospective \/ Legacy <\/h2>\n<p>Lest there be any confusion, this isn&#8217;t some newly introduced foible.  It has been this way &#8211; literally, identical in every respect &#8211; since at least Delphi 7 and current as of at least Seattle.<\/p>\n<h2> Conclusion <\/h2>\n<p>This one&#8217;s simple:  I think if you don&#8217;t mind I&#8217;ll stick to my Boolean-indexed arrays.  \ud83d\ude42<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">5<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> Jaap Van Goor was asking on FaceBook about some seemingly strange behaviour when obtaining string representations of Booleans using the Delphi RTL, which led me to revisit some familiar (and some not so familiar) old Delphi ground and take a look at the area involved in further detail.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[4],"tags":[292,13],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-FG","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":2597,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2597\/","url_meta":{"origin":2584,"position":0},"title":"(True = 1) and (True = &#8216;-1&#8217;) ?","date":"17 Mar 2017","format":false,"excerpt":"It has been observed that the Delphi documentation states that the constants True and False have the values 1 and 0 respectively, not the -1 and 0 that the default string conversions apply. This does actually make sense but also lays a trap for the unwary. True, True, Wherefore Art\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":507,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/507\/","url_meta":{"origin":2584,"position":1},"title":"Mind the Gap(s)","date":"14 Sep 2009","format":false,"excerpt":"This is what I love about Delphi. \u00a0After almost 15 years of Delphi'ing there's still new things to learn, and I don't just mean new features in the latest releases. \u00a0I mean, basic, fundamental things that have been there for years, just undiscovered (by me). \u00a0In this case the discovery\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1397,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1397\/","url_meta":{"origin":2584,"position":2},"title":"Qualified Enum Reference That Fails to Compile in XE4 (and rightly so)","date":"01 May 2013","format":false,"excerpt":"Running through some of my code last night, putting them through the new XE4 compiler, threw up a real oddity: Some code that used to compile just fine, which no longer compiles in XE4 and which should not have compiled before! It's an odd one, because the code previously compiled\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2624,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2624\/","url_meta":{"origin":2584,"position":3},"title":"Expressive If and Case &#8230;","date":"25 Apr 2017","format":false,"excerpt":"A quick post on a small but hugely useful little language feature in Oxygene... if expressions. Many people will be familiar with the so-called ternary operator. Delphi developers will also be aware that there is no direct equivalent in Delphi. In 'C' and other languages we can write a statement\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":3024,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/3024\/","url_meta":{"origin":2584,"position":4},"title":"Scope Elevation: Creating A &#8216;Pseudo-Namespace&#8217;","date":"31 Oct 2019","format":false,"excerpt":"In my previous post I talked about how \"namespaces\" in Delphi really don't exist for any practical purposes normally associated with the concept. Having become familiar with the concept in other languages I found I was missing them, so I devised a way to obtain some of the benefits, despite\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/niklaus-wirth-edit.jpg?fit=934%2C362&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":1930,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1930\/","url_meta":{"origin":2584,"position":5},"title":"VCL Threading &#8211; Indeterminate Lifetimes","date":"18 Oct 2013","format":false,"excerpt":"Sometimes when you launch a thread you don't know when it will complete whatever processing it is tasked with. Sometimes you do. Sometimes it may never complete and will require that you expressly terminate it. Usually any given thread will have a lifecycle that is at least consistently one or\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/2584"}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=2584"}],"version-history":[{"count":11,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/2584\/revisions"}],"predecessor-version":[{"id":2596,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/2584\/revisions\/2596"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=2584"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=2584"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=2584"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}