Why the difference between set-if and if...set?

The following example shows a (to me) unexpected difference between set-if and if…set. I expected the same behavior in both instances.

Could somebody explain to me what is happening here?

(Same results with versions from 0.10 to 0.14 rc.)

 #let ff(indent: true, body) = {
 set par(first-line-indent: 2em) if indent == true 
 set par(first-line-indent: 0em) if indent == false 
 body
}
#let gg(indent: true, body) = {
  if indent == true {
    set par(first-line-indent: 2em)
  } else {
    set par(first-line-indent: 0em)
  }
  body
}

== F
=== True
  #ff[Expect indent#parbreak()Indented]

=== False
  #ff(indent: false)[Expect no indent#parbreak()Not indented]

== G
=== True
  #gg[Expect indent#parbreak()No indent---why?]

=== False
  #gg(indent: false)[Expect no indent#parbreak()Not indented]

Results in:

set-if exists just for this use case, so set with if is the only correct way.

The reason is that set only has effect until the end of the current scope.

Where does the current scope end? At the next }, so both sets have no effect (apply to nothing).

It’s mentioned here in the docs Styling – Typst Documentation but it’s a point that’s worth bringing up explicitly like this.

With an example:

{
  set par(first-line-indent: 2em)
  /* The set applies to any document part located here */
}
// The above `set` has no effect on what follows here

It doesn’t matter if it’s a [] or {} or other delimited scope, the same rule applies.

3 Likes

We can rewrite the function gg in this way, and that’s also something that’s used sometimes, and then it will have the originally intended effect:

#let gg(indent: true, body) = {
  if indent {
    set par(first-line-indent: 2em)
    body
  } else {
    set par(first-line-indent: 0em)
    body
  }
}
1 Like